泰凌Wi-Fi SDK 开发手册
SDK入门指南
本文档为Wi-Fi SDK的开发指南,适⽤于泰凌TLSR9118芯片。
简介
SDK入门指南介绍了SDK基础知识:如何设置开发环境、构建SDK和托管驱动程序(如果需要),以及如何下载和运行固件。
由泰凌微电子提供的SDK文件包含以下目录。
- 文档
- 软件
- 工具链
设置开发环境
为构建TLSR9118设备固件,推荐在64位Linux PC上进行。建议使用Ubuntu 20.04或更高版本。在接下来的章节中,假设用户使用的是Ubuntu。
安装Linux软件包
为了进行基本构建,请安装以下软件包。
$ sudo apt install build-essential libncurses-dev
$ sudo apt-get install libevent-dev libnl-3-dev libnl-genl-
3-dev
工具链
将工具链解压缩到“/opt/”目录中。
$ sudo tar xvf nds32le-elf-mculib-v5.tar.gz -C /opt/
默认情况下, SDK假定工具链路径为“/opt/nds32le-elf-mculib-v5/bin/”。如果工具链被解压到其他位置,则在继续构建之前,必须使用“make menuconfig”相应地更改路径。

Python和软件包
构建过程中的某些步骤涉及运行Python脚本。安装以下Python软件包。
$ sudo apt install python3-pip
$ pip install pycryptodome
$ pip install imgtool
由于这些软件包通常安装在/home/{user}/.local/bin目录下,所以必须将该路径添加到PATH变量中。
编辑~/.bashrc文件,并添加以下行。
export PATH=$PATH:~/.local/bin
另一种方法是在目录下创建一个名为bin的文件夹,Linux发行版会自动将/bin和/.local/bin添加到PATH中。
在Windows上安装AICE驱动
为使用JTAG进行调试,请确保已连接AICE适配器。
- 安装AICE驱动程序
终端应用程序
需要一个串行终端应用程序来:
- 通过UART下载固件
- 查看控制台日志
构建固件
目录结构
SDK的顶级目录包含以下子目录。
| 目录 | 描述 |
|---|---|
| api | Telink Wi-Fi APIs |
| app | 示例应用程序 |
| configs | 默认配置 |
| hal | 硬件抽象层 |
| include | 需要包含的头文件 |
| kernel | 带有FreeRTOS和FreeBSD的内核 |
| lib | 可能使用到的库模块 |
| prebuilt | 使用预定义配置构建的库 |
| scripts | 与构建相关的脚本 |
TLSR9118芯片内部集成了一个ROM,其中包含一些常用的软件组件,如C库、操作系统、 WLAN驱动、网络库和BLE控制器相关接口。部分软件组件已经集成在ROM中并已具体实现,而在发布的SDK中,这些组件仅提供了头文件。
构建
如果使用IDE,推荐使用VS Code插件形式,使用VS Code编译需要下载"Telink development tool",具体的工具下载及SDK编译,请参考泰凌文档网站中的"泰凌VS Code扩展应用"的文档;如果使用命令行,按照如下方式进行:
使用make distclean命令以清除上次构建的残留文件。在更改任何配置之前,最好先清理文件。
在配置目录中选择一个合适的配置文件作为构建的起点。构建一个在RAM中运行的引导加载程序。
$ make distclean
$ make tlsr9xxxs_bl_ram_defconfig
$ make
这将产生wits.tlsrboot.ram.bin
构建一个用于固件烧录的引导加载程序
$ make distclean
$ make tlsr9xxxs_bl_defconfig
$ make
这将产生 wits.tlsrboot.bin
构建Standalone模式的固件
$ make distclean
$ make tlsr9xxxs_defconfig
$ make
这将生成wits.mcuboot.bin
构建过程完成后,将生成wits.xxxx.bin文件。二进制文件名根据所用的配置可能会稍有不同。
修改配置
构建系统基于传统的Kconfig和Kbuild。如果需要更改任何配置选项,请运行make menuconfig,并导航到需要修改的选项。
$ make menuconfig

安全引导
支持安全引导,只允许启动经过验证的固件。
如需更多信息,请联系Telink技术支持。
闪存加密
支持闪存加密以保护固件。即使将固件烧录到另一个TLSR9118芯片,该固件也将无法使用。必须使用相应的安全凭据写入eFuse。
有关更多信息,请联系Telink技术支持。
构建主机(Host)驱动程序
本节仅适用于希望通过SDIO或USB将TLSR9118作为Wi-Fi接口连接到主机平台的用户。
若TLSR9118与主机平台连接,必须在主机上安装并运行相应的内核驱动程序。
构建
配置文件选择与设置
cd xiaohu-ax/
cp configs/cfg_xxx.mk cfg.mk
由于支持多种平台和配置模式,如SDIO OOB模式、SDIO INT模式、USB模式等。用户要依据平台与需求首先选择一个配置文件进行编译。系统默认提供配置文件保存在 configs/目录下:
| 配置文件 | 平台/SDIO模式 |
|---|---|
| cfg_sdio_normal_fullhan.mk | Fullhan, 4 bit mode interrupt |
| cfg_sdio_normal_goke.mk | Goke, 4 bit mode interrupt |
| cfg_sdio_normal.mk | X86/X64, 4 bit mode interrupt |
| cfg_sdio_polling.mk | Linux PC, Polling mode |
| cfg_sdio_oob_int_goke.mk | Goke, OOB interrupt mode |
| cfg_usb.mk | X86/X64 USB模式 |
编译驱动sncmfmac.ko
虽然ARCH和CROSS_COMPILE在文件中已经配置好,但参数KDIR需要根据用户环境进行明确指定。其中,KDIR=/home/apache/page/xxx是指定的kernel所在位置。
make KDIR=/home/apache/page/linux-4.9
如果没有指定KDIR,系统则会使用默认的内核KDIR,这将编译出的驱动将适用于X86/X64平台上运行。
make
编译应用程序
make KDIR=/home/apache/page/linux-4.9 apps
目前有两种类型的应用程序:
- 'sncm_cmd':通过主机命令进行Wi-Fi配置和控制。
- 'sncm_chn'(也称为 TlsrChannel): Wi-Fi配置和控制在TLSR9118侧完成,通过Channel获取Wi-Fi连线信息。
- 'sample_link'主要用于同步TLSR9118侧网络节点的Mac地址、IP地址等信息。
- 'sample_cli'主要用于发送客户自定义的信息。
下载镜像
下载镜像,需要使用"Telink Burning and Debugging Tool (BDT)"工具进行烧录,具体的BDT 使用指南,请参考泰凌文档网站中的"BDT烧录调试工具"文档。
调试
JTAG调试使用OpenOCD
概述
AndeShape AICE-MICRO是一个基于FT2232H的JTAG调试设备,与AndeSight™开发套件和AndesCore V5系列兼容,并支持OpenOCD的JTAG接口。

设置调试环境
将JTAG接口连接到TLSR9118 EVB板,上电即可激活JTAG功能。
网络拓扑如下图所示。

远程调试
(1) 在远程计算机上运行openocd.exe守护程序。

在编译服务器上,使用nds32le-elf-mculib-v5安装riscv32-elf-gdb。
使用以下命令进行远程调试:
/opt/nds32le-elf-mculib-v5/bin/riscv32-elf-gdb
target remote 10.12.7.102:1234
说明:IP地址10.12.7.102是用于连接JTAG的主机地址,而1234是目标端口。

Wi-Fi软件开发指南
概述
TLSR9118为应用层提供了丰富的TLSR API接口,使其能够操作和控制WLAN驱动。通过TLSR API,开发者可以实现与Wi-Fi相关的功能,例如创建 STA/SoftAP、网络扫描、网络配置、关联、解除关联以及状态查询,如图所示。

以下是各功能模块的说明:
- APP 应用: 用户可基于 TLSR API 进行二次开发。
- Demo APP: SDK 提供的功能开发示例。
- TLSR API: 基于SDK提供的通用接口。
- IFCONFIG: 用于配置、控制和查询网络接口。
- LWIP: 网络协议栈。
- WPA SUPPLICANT(含HOSTAPD): Wi-Fi 管理模块。
- WLAN 驱动: 实现 802.11 网络协议的模块。
- HW: WLAN的硬件层实现。
Wi-Fi加载
概述
当芯片上电后,WLAN驱动将自动完成加载,并进行寄存器的初始配置、参数校准以及软件资源的申请和配置。
开发流程
步骤 1: 芯片上电后, WLAN 自动加载成功。
步骤 2: 请参考章节Wi-Fi STA功能或Wi-Fi SoftAP功能。
步骤 3: 结束
注意事项
WLAN驱动默认会自动加载wlan0和wlan1所需的软件资源。
Wi-Fi STA功能
概述
STA 提供以下功能
- WPA_SUPPLICANT 中为 STA 配置所需资源
- 基础和进阶的网络扫描
- 网络关联
- DHCP 功能
- 查询关联 AP 的状态
- 去关联
开发流程
使用场景
当需要连接到某个网络并与其通信时,应启动 STA 功能。
STA API功能
STA功能提供的API接口如下表所示:
| API名称 | 描述 |
|---|---|
| tlsr_wifi_sta_start | 启动 STA。 |
| tlsr_wifi_register_event_callback | 注册 STA 接口的事件回调函数。 |
| tlsr_wifi_unregister_event | 取消注册接口的事件回调函数。 |
| tlsr_wifi_event_send | 传送注册 event 到 host(非必要函数) 。 |
| tlsr_wifi_sta_scan | 触发 STA 扫描 AP。 |
| tlsr_wifi_sta_advance_scan | 依据特定参数值型扫描。 |
| tlsr_wifi_sta_scan_results | 获取 STA 扫描结果。 |
| tlsr_wifi_sta_set_config | 配置 STA 模式下的 Wi-Fi 连接信息。 |
| tlsr_cli_sta_reconnect_policy | 设置 STA 模式下的自动重连配置。 |
| tlsr_wifi_sta_connect | 执行 STA 连线。 |
| tlsr_wifi_sta_get_connect_info | 获取已连接 STA 的网络状态。 |
| tlsr_wifi_sta_get_ap_rssi | 获取路由器的接收信号强度指示(RSSI)值。如果未连接, 则返回0xFF。 |
| netifapi_dhcp_start | 启动 DHCP Client,并且获取 IP 地址。 |
| netifapi_dhcp_stop | 停止 DHCP Client。 |
| tlsr_wifi_sta_disconnect | 执行 STA 断线。 |
| tlsr_wifi_sta_fast_connect | 执行 STA 快速连接,用于 WPA/WPA2 加密的路由器。 |
| tlsr_cli_sta_get_psk | 获取预共享密钥(PSK)信息以快速连接。 |
| tlsr_wifi_sta_stop | 关闭 STA。 |
| tlsr_wifi_sta_set_ps | 设置 STA 模式下的省电模式(Power Save)配置。 |
| tlsr_wifi_sta_set_country_code | 设置期望的国家代码。 |
| tlsr_wifi_sta_get_country_code | 获取当前设置的国家代码。 |
| tlsr_wifi_sta_set_keepalive | 启用/禁用保持活动功能,根据间隔发送空帧。 |
| tlsr_wifi_wc_set_keepalive | 在STA进入低功耗模式时,启用/禁用保持活动功能。 |
| wits_wifi_set_wc_bcn_loss_chk | 在STA进入低功耗模式时,启用/禁用信标丢失的最后检查。 |
| wits_wifi_set_wc_port_filter | 在STA进入低功耗模式时,启用/禁用TCP/UDP端口号过滤。 |
实现流程
实现步骤如下:
步骤 1: 调用tlsr_wifi_register_event_callback 注册 STA 事件回调函数。
步骤 2: 调用tlsr_wifi_sta_start 启动 STA。
步骤 3: 执行 STA 扫描,可以选择调用tlsr_wifi_sta_scan 或tlsr_wifi_sta_advance_scan。
步骤 4: 通过tlsr_wifi_sta_scan_results获取扫描结果。
步骤 5: [可选]调用tlsr_cli_sta_reconnect_policy设定自动重连策略。
步骤 6: 依据第 4 步的扫描结果, 选择适当的网络并使用tlsr_wifi_sta_set_config配置连线设置。
步骤 7: 调用tlsr_wifi_sta_connect进行网络连接。
步骤 8: 在收到SYSTEM_EVENT_STA_CONNECTED事件后,可以调用tlsr_wifi_sta_get_connect_info查询网络状态。
步骤 9: 调用netifapi_dhcp_start获取 IP 地址。
步骤 10: 调用tlsr_wifi_sta_disconnect断开连接。
步骤 11: 调用 tlsr_wifi_sta_stop 关闭 STA。
步骤 12: 结束
API函数返回值如下表所示。
| 定义 | 数值 | 描述 |
|---|---|---|
| WITS_OK | 0 | 执行成功 |
| WITS_FAIL | -1 | 执行失败 |
注意事项
连线相关事项
- 事件回调:执行 tlsr_wifi_register_event_callback 函数是必要的,以便清晰地了解 STA 发生的事件并执行相应的动作。
- 带宽支持:
- 在 Wi-Fi 4 模式下,本产品支持 BW40 和 BW20。
- 在 Wi-Fi 6 模式下,本产品仅支持 BW20。
- 连线接口:连线采用非阻塞式接口。可以通过接收 SYSTEM_EVENT_STA_CONNECTED 事件来确认是否成功连接。
- 直接连接:如果已知待连接网络的参数,可以直接发起连线,无需进行扫描过程。
- 认证模式:
- tlsr_wifi_sta_fast_connect 接口的 auth 参数仅支持以下认证模式:
- TLSR_WIFI_SECURITY_WPAPSK
- TLSR_WIFI_SECURITY_WPA2PSK
- 基于联盟规范和安全性的考虑,以下认证模式不受支持:
- WEP
- WPA2PSK + TKIP
- tlsr_wifi_sta_fast_connect 接口的 auth 参数仅支持以下认证模式:
扫描相关事项
- 扫描采用非阻塞式接口,当扫描命令下发成功后,建议延迟 1 秒后再获取扫描结果,或等待 SYSTEM_EVENT_SCAN_DONE 事件来确认扫描已结束。
- 可通过指定SSID、 BSSID、 Channel 等参数进行特定的扫描(参考tlsr_wifi_sta_advance_scan)
编程实例
示例: 实现STA功能的启动、关联、获取网络信息和IP地址。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hal/unaligned.h>
#include <hal/kernel.h>
#include <hal/wlan.h>
#include <hal/kmem.h>
#include "kernel.h"
#include "compat_if.h"
#include "if_media.h"
#include "sncmu_d11.h"
#include "fwil_types.h"
#include "fweh.h"
#include "scdc.h"
#include "task.h"
#include "FreeRTOS.h"
#include <net80211/ieee80211_var.h>
#include <wits_err.h>
#include <wits_log.h>
#include <wits_wifi.h>
#include <wits_event_loop.h>
#include <common.h>
#include <tlsr_wifi.h>
#define TLSR_NEED_DHCP_START(event) ((event)->event_info.connected.not_en_dhcp == false)
/* 待连接的网络资讯,可在编译期间进行选定或是在执行期间进行修改 */
tlsr_wifi_assoc_request g_assoc_req = {
.ssid = "Xiaomi_7AB6", /* 网络名称 */
.auth = TLSR_WIFI_SECURITY_WPA2PSK, /* 认证模式 */
.key = "12345678", /* 认证密码 */
};
int tlsr_wifi_register_event_callback(system_event_cb_t event_cb, void *priv)
{
wits_event_loop_init(event_cb, priv);
return WITS_OK;
}
/* 此函数可以接收 Wi-Fi 相关必要事件 */
wits_err_t event_handler(void *ctx, system_event_t * event)
{
switch (event->event_id) {
case SYSTEM_EVENT_STA_START:
break;
case SYSTEM_EVENT_STA_STOP:
break;
case SYSTEM_EVENT_STA_GOT_IP:
printf("\r\n WIFI GOT IP indicate\r\n");
break;
case SYSTEM_EVENT_AP_START:
break;
case SYSTEM_EVENT_AP_STOP:
break;
case SYSTEM_EVENT_AP_STADISCONNECTED:
break;
case SYSTEM_EVENT_STA_CONNECTED:
printf("\r\nWIFI CONNECTED indicate\r\n");
/* 成功连线, 启动 DHCP client */
if (TLSR_NEED_DHCP_START(event)) {
tlsr_wifi_status connect_status;
netifapi_dhcp_start(tlsr_wifi_get_netif(WITS_IF_WIFI_STA));
tlsr_wifi_sta_get_connect_info(&connect_status);
tlsr_wifi_sta_dump_ap_info(&connect_status);
}
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
printf("\r\nWIFI DISCONNECT\r\n");
break;
case SYSTEM_EVENT_SCAN_DONE:
printf("WiFi: Scan results available\n");
break;
case SYSTEM_EVENT_TLSR_CHANNEL:
printf("WiFi: Tlsr channel send msg\n");
tlsr_wifi_event_send(event, sizeof(system_event_t));
break;
default:
break;
}
return WITS_OK;
}
int tlsr_wifi_start_connect(void)
{
tlsr_wifi_assoc_request *assoc_req = &g_assoc_req;
/* 配置连线资讯 */
if (tlsr_wifi_sta_set_config(assoc_req, NULL))
return WITS_FAIL;
return tlsr_wifi_sta_connect();
}
int main(void)
{
int ret = WITS_OK;
char ifname[WIFI_IFNAME_MAX_SIZE + 1] = {0};
int len = sizeof(ifname);
printf("Sta Hello world!\n");
/* 注册事件回调函数 */
tlsr_wifi_register_event_callback(event_handler, NULL);
/* 启动 STA 功能 */
tlsr_wifi_sta_start(ifname, &len);
/* 启动 STA 连线 */
ret = tlsr_wifi_start_connect();
/* ret 为 0 表示执行成功 */
return ret;
}
Wi-Fi SoftAP功能
概述
SoftAP 功能主要包括
- 在 WPA_SUPPLICANT 中为 SoftAP 配置所需资源
- 网络配置
- DHCP 服务器功能
- 查询关联的 STA 状态
- 断开指定的 STA
开发流程
使用场景
当需要创建一网络接入点,供其他设备接入并共享网络资源时, 应启动SoftAP功能。
SoftAP API功能
SoftAP 功能提供的API接口如下表所示。
| API名称 | 描述 |
|---|---|
| tlsr_wifi_sap_start | 启动 SoftAP。 需先调用 tlsr_wifi_sap_set_config配网。 |
| tlsr_wifi_sap_stop | 关闭 SoftAP。 |
| tlsr_wifi_register_event_callback | 注册接口的事件回调函数。 |
| tlsr_wifi_unregister_event | 取消注册接口的事件回调函数。 |
| tlsr_wifi_sap_set_config | 配置 SoftAP Wi-Fi 连线所需信息。 |
| tlsr_wifi_sap_get_config | 获取 SoftAP 当前配置 |
| tlsr_wifi_sap_set_beacon_interval | 设置 SoftAP 的 beacon 间距 |
| tlsr_wifi_sap_set_dtim_period | 设置 SoftAP 的 dtim 周期 |
| tlsr_wifi_sap_get_connected_sta | 获取当前接入的 STA 信息 |
| tlsr_wifi_sap_deauth_sta | 断开指定 STA 的连信息 |
| tlsr_wifi_set_ip | 设置 SoftAP 的 IP 地址、子网掩码和网关参数。 |
| tlsr_wifi_reset_ip | 清除 SoftAP 的 IP 地址、子网掩码和网关参数。 |
| netifapi_dhcps_start | 启动 DHCP Server。 |
| netifapi_dhcps_stop | 停止 DHCP Server。 |
实现流程
实现步骤如下:
步骤 1: 调用tlsr_wifi_register_event_callback注册 SoftAP 事件回传函数。
步骤 2: 调用tlsr_wifi_sap_set_config配置 SoftAP 的网络参数。
- 调用
tlsr_wifi_sap_set_beacon_interval设置 beacon 间距。 - 调用
tlsr_wifi_sap_set_dtim_period设置 dtim 周期。 tlsr_wifi_sap_set_config将自行调用tlsr_wifi_sap_stop,tlsr_wifi_sap_start以启用 SoftAP。
步骤 3: 调用tlsr_wifi_sap_stop,关闭之前的 SoftAP。
步骤 4: 调用tlsr_wifi_sap_start,重新启动 SoftAP。
步骤 5: 调用tlsr_wifi_set_ip配置网络 IP。
步骤 6: 调用netifapi_dhcps_start启动 DHCP 服务器。
步骤 7: 调用netifapi_dhcps_stop停止 DHCP 服务器。
步骤 8: 调用tlsr_wifi_sap_stop关闭 SoftAP。
步骤 9: 结束
API函数返回值如下表所示。
| 定义 | 数值 | 描述 |
|---|---|---|
| WITS_OK | 0 | 执行成功 |
| WITS_FAIL | -1 | 执行失败 |
注意事项
- 为了清晰地了解 SoftAP 发生的事件并执行相应的动作,执行 tlsr_wifi_register_event_callback 函数是必要的。
- SoftAP 的网络参数可以预先设置为默认值。
- 关闭 SoftAP 时,其网络参数不会被重置,但重启设备后会恢复到初始默认值。
- SoftAP 只支持 OPEN 和 WPA2 模式。
- SoftAP 模式下,最大关联用户数不超过 1 个。
编程实例
示例: 实现SoftAP功能启动、获取网络信息、设置IP。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hal/unaligned.h>
#include <hal/kernel.h>
#include <hal/wlan.h>
#include <hal/kmem.h>
#include "kernel.h"
#include "compat_if.h"
#include "if_media.h"
#include "sncmu_d11.h"
#include "fwil_types.h"
#include "fweh.h"
#include "scdc.h"
#include "task.h"
#include "FreeRTOS.h"
#include <net80211/ieee80211_var.h>
#include <wits_err.h>
#include <wits_log.h>
#include <wits_wifi.h>
#include <wits_event_loop.h>
#include <common.h>
#include "dhcps.h"
#include <tlsr_wifi.h>
/* 设置的网络资讯, 可在编译期间进行选定或是在执行期间进行修改 */
tlsr_wifi_softap_config g_sap_cfg = {
.ssid = "sap_test",
.key = "12345678",
.channel_num = 6,
.authmode = TLSR_WIFI_SECURITY_WPA2PSK,
.pairwits = TLSR_WIFI_PAIRWITS_AES,
};
wits_err_t event_handler(void *ctx, system_event_t * event)
{
switch (event->event_id) {
case SYSTEM_EVENT_AP_START:
printf("\r\nSYSTEM_EVENT_AP_START\r\n");
/* 设置网络 IP */
tlsr_wifi_set_ip("wlan1", "192.168.200.1", "255.255.255.0", NULL);
/* 启动 DHCP Server */
netifapi_dhcps_start(tlsr_wifi_get_netif(WITS_IF_WIFI_AP));
break;
case SYSTEM_EVENT_AP_STOP:
printf("\r\nSYSTEM_EVENT_AP_STOP\r\n");
break;
case SYSTEM_EVENT_AP_STACONNECTED:
printf("\r\nSYSTEM_EVENT_AP_STACONNECTED\r\n");
printf("Connected STA:" MACSTR "\r\n",
MAC2STR(event->event_info.sta_connected.mac));
break;
case SYSTEM_EVENT_AP_STADISCONNECTED:
printf("\r\nSYSTEM_EVENT_AP_STADISCONNECTED\r\n");
printf("Disconnected STA:" MACSTR "\r\n",
MAC2STR(event->event_info.sta_disconnected.mac));
break;
default:
break;
}
return WITS_OK;
}
int main(void)
{
int ret = WITS_OK;
char ifname[WIFI_IFNAME_MAX_SIZE + 1] = {0};
int len = sizeof(ifname);
tlsr_wifi_softap_config *sap = &g_sap_cfg;
printf("SoftAP Hello world!\n");
/* 注册事件回调函数 */
tlsr_wifi_register_event_callback(event_handler, NULL);
/* 设置 SoftAP */
tlsr_wifi_sap_set_config(sap);
/* 启动 SoftAP */
ret = tlsr_wifi_sap_start(ifname, &len);
/* ret 为 0 表示执行成功 */
return ret;
}
Wi-Fi STA/SoftAP共存
概述
STA与SoftAP共存意味着STA功能和SoftAP功能可以同时运行。根据STA和SoftAP的启动顺序,可以分为以下几种场景:
| 场景 | 描述 |
|---|---|
| 同频同带共存 | 全时共存 |
| 同频异带共存 | 全时共存 |
| 异频同带共存 | 平均分时共存 |
| 异频异带共存 | 平均分时共存 |
全时共存: STA和SoftAP可以同时工作。
分时共存: STA和SoftAP会在各自的时间段内工作。
开发流程
使用场景
在网络配置时,产品首先启动SoftAP。当手机连接到SoftAP 后,通过手机APP发送家庭网络信息(如 SSID 和密码)给产品。当产品接收到这些信息后,它会启动STA并连接到家庭网络。一旦产品成功连接,它会关闭SoftAP,并仅保持STA 与家庭网络的连接。其他共存场景可以根据产品的具体需求来决定。
实现流程
步骤 1: 创建 SoftAP 网络接口 (详见章节Wi-Fi SoftAP功能) 。
步骤 2: 手机连接到 SoftAP 网络,并通过手机 APP 发送家庭网络信息。
步骤 3: [非必要]为了避免分时共存,建议重新启动 SoftAP 至家居网络的信道 (详见章节Wi-Fi SoftAP功能)。
步骤 4: 创建 STA,并根据家居网络信息(SSID 和密码),完成关联(详见章节Wi-Fi STA功能)。
步骤 5: 关闭 SoftAP(详见章节Wi-Fi SoftAP功能)。
步骤 6: 结束
返回值: 请参考对应模块功能的返回值说明
注意事项
- 在分时共存模式下,由于STA和SoftAP需要轮流使用,性能可能会受到影响。为了获得更好的性能,建议将SoftAP设置在STA的工作信道上。
编程实例
请参考章节Wi-Fi STA功能及Wi-Fi SoftAP功能编程实例。
混杂模式
概述
混杂模式允许Wi-Fi硬件捕捉指定频道上的所有Wi-Fi帧,并可以发送原始的802.11帧。此模式对于高级网络监控和问题诊断是极为关键的。
应用场景
应用场景
通过使用通用套接字接口来捕获由硬件接收的Wi-Fi原始帧,相比于使用回调注册,此方法在数据路径应用中提供了更高的一致性和可靠性。
混杂模式API功能
以下是混杂模式支持的API功能列表:
| API名称 | 描述 |
|---|---|
| tlsr_wifi_set_promiscuous | 激活混杂模式,用于监控和捕获流量。 |
| tlsr_wifi_get_promiscuous | 查询当前是否启用了混杂模式。 |
| tlsr_wifi_80211_tx | 在指定频道上发送原始的IEEE 802.11数据帧。 |
| tlsr_wifi_set_channel | 配置设备的主/副频道。 |
| tlsr_wifi_get_channel | 获取当前设备设置的主/副频道信息。 |
实施步骤
按以下步骤实现混杂模式:
(1) 通过tlsr_wifi_set_promiscuous启用混杂模式。
(2) 使用tlsr_wifi_set_channel配置频道。
(3) 通过tlsr_wifi_80211_tx发送原始802.11数据帧。
(4) 可选:利用RAW套接字从频道捕获802.11数据帧,进行进一步分析。
| 定义 | 数值 | 描述 |
|---|---|---|
| WITS_OK | 0 | 执行成功 |
| WITS_FAIL | -1 | 执行失败,需检查配置及硬件状态。 |
注意事项
运行模式注意
在启用混杂模式前,确保停用STA和SoftAP模式,可以通过执行tlsr_wifi_sta_stop和tlsr_wifi_sap_stop命令来实现。仅在混杂模式激活时,tlsr_wifi_80211_tx和tlsr_wifi_set_channel等API才会正常工作。
监控模式下的信道设置
在每次启用混杂模式时,必须设置RF信道。例如,以下操作顺序是无效的:
(1) 在wlan0上启用混杂模式。
(2) 将wlan0的信道设置为1。
(3) 在wlan1上启用混杂模式。
(4) 将wlan1的信道设置为6。
(5) 在wlan0上发送一个原始的802.11帧,期望它会在信道1上发送。
由于TLSR9118s只有一个RF链路,第4步中的设置会覆盖之前的信道设置,因此在第5步中RF信道将会被设置为6。
编程示例
示例1:
向6频道发送一个原始802.11数据帧,具体实现方法可参考api/tlsr_cli_wifi.c文件。确保已启用CONFIG_CLI_WIFI_CHANNEL和CONFIG_CLI_WIFI_MONITOR以使用相关CLI命令。

示例2:
使用‘wshark’命令显示接收到的802.11原始数据帧。演示此功能前需要启用CONFIG_CMD_WSHARK。


Wi-Fi常见问题FAQ
TBD
设备驱动开发指南
引言
设备驱动开发指南为开发者提供了一份全面的指南,主要介绍如何通过TLSR9118设备驱动来实现SoC(System on Chip,片上系统)外设的应用。文档的目的是帮助开发者充分利用TLSR9118 Wi-Fi 6和BLE 5低功耗SoC的功能,重点指导如何有效使用设备驱动的应用程序接口(API),并将它们集成到对性能要求极高的应用中。
设备驱动API
TLSR9118 SDK 提供了两种不同层级的 API:
-
低层 API
- 代码位置: hal/drivers/xxxx/xxxx.c
- 头文件位置: include/hal/xxxx.h
- 功能目的:这些 API 主要提供硬件抽象功能,尤其是直接访问硬件寄存器。
-
应用层 API(TLSR API)
- 代码位置: api/tlsr_xxxx.c
- 头文件位置: api/include/tlsr_xxxx.h
- 功能特点: TLSR API旨在通过通用功能函数简化对功能的访问,这些函数封装了对一个或多个低层 API、操作系统功能及基本错误处理的调用。
对性能要求极高的应用
尽管TLSR API通过在低层 API 之上添加一个抽象层来简化开发过程,但这可能会带来一些性能上的开销。在资源密集型或对时间极为敏感的应用中,推荐直接使用低层 API。举个例子,在需要通过 SPI 和 I2C 接口与外部集成电路(IC)同时进行通信的自定义电路板中,建议开发一个专门针对 SPI 和 I2C 低层 API 的定制设备驱动程序。应用程序可以直接与这个定制驱动程序进行交互,而不是使用更高层次的TLSR API。
驱动程序事件通知回调
在TLSR9118 SDK中,有些外设完成用户发起的操作可能需要一些时间。为此,SDK提供了两种类型的API:
同步API (tlsr_xxxx):这类 API 包含一个超时参数,适合于那些应用程序能够在指定时间内等待操作完成的场景。
异步API (tlsr_xxxx_async):这类API不包含超时参数,更适合那些不宜等待操作完成的场景。这些操作的完成情况,会通过特定的通知回调机制来告知。在进行异步操作时,操作完成的通知是通过回调函数来传递的。这些回调函数在中断处理过程中被触发,并且应当迅速执行,以免影响到中断处理的效率。值得注意的是,由于这些回调函数运行的特定环境,其中使用其它API可能会受到限制。通常情况下,出于简单易用和易于集成的考虑,大多数情况下推荐使用同步API。
引脚控制
SoC(片上系统)的许多外设,例如 SPI、 I2C、 GPIO 和 PWM,都需要使用特定的引脚。
引脚复用选项
可用的引脚复用选项在以下文件中有详细列出:
- 文件位置:hal/drivers/pinctrl/pinctrl-tlsr9118.c
每个 SoC 引脚都可以被配置为若干预先定义好的功能之一。以 UART0 和 UART1 为例,它们的引脚复用选项如下所示:
#ifdef CONFIG_USE_UART0
struct pinmux uart0_pin_mux[] = {
MUX("cts", 19, 3),
MUX("rts", 20, 3),
MUX("rxd", 0, 4),
MUX("rxd", 21, 0),
MUX("txd", 1, 4),
MUX("txd", 22, 0),
MUX("txd", 8, 4),
};
#endif
#ifdef CONFIG_USE_UART1
struct pinmux uart1_pin_mux[] = {
MUX("cts", 19, 4),
MUX("rts", 20, 4),
MUX("rxd", 0, 0),
MUX("rxd", 6, 4),
MUX("rxd", 17, 4),
MUX("txd", 1, 0),
MUX("txd", 7, 4),
MUX("txd", 8, 3),
MUX("txd", 18, 4),
};
#endif
引脚复用选择
引脚的选择依据具体的电路板设计而定,因为每种设计使用引脚的方式都不尽相同。比如,在TLSR9118评估板中, UART引脚的选择如下,文件位置为:
- 文件位置:hal/board/tlsr9118-evb-qfn40/board.c
static struct pinctrl_pin_map pin_map[] = {}
#ifdef CONFIG_USE_UART0
/* UART0 */
pinmap(21, "atcuart.0", "rxd", 0),
pinmap(22, "atcuart.0", "txd", 0),
#endif
#ifdef CONFIG_USE_UART1
/* UART1 */
pinmap( 0, "atcuart.1", "rxd", 0),
pinmap( 1, "atcuart.1", "txd", 0),
#endif
一般来说,外设驱动程序,如UART驱动程序,会自动请求引脚驱动程序配置这些引脚复用。
UART
概述
TLSR9118 SoC支持使用通用异步接收发射器(UART)与外部设备通信。SoC提供三个UART实例,其中一个可配置为控制台,以协助固件开发。
API功能
- 初始化和配置:
- tlsr_uart_init():初始化UART模块。
- tlsr_uart_deinit():去初始化UART模块。
- tlsr_uart_reset():重置UART模块。
- 发送器和接收器:
- 数据传输:tlsr_uart_tx(),tlsr_uart_tx_async()
- 数据接收:tlsr_uart_rx(),tlsr_uart_rx_async()
开发指南
配置结构
struct tlsr_uart_cfg {
enum tlsr_uart_buadrate buadrate;
enum tlsr_uart_data_bits data_bits;
enum tlsr_uart_parity parity;
enum tlsr_uart_stop_bits stop_bits;
uint8_t dma_en;
};
- baudrate: 设置波特率(从50到115200)。
- data_bits: 设置数据位长度(5、6、7或8位)。
- parity: 配置奇偶校验(选项:无校验、奇校验、偶校验)。
- stop bits: 设置停止位长度(1位或2位)。
- dma_en: 启用或禁用DMA传输。
事件处理
- TLSR_UART_EVENT_TX_CMPL: 当使用异步API完成UART传输时触发。
- TLSR_UART_EVENT_RX_CMPL: 当使用异步API完成UART接收时触发。
传输 (Tx) 和接收 (Rx) 的操作步骤:
(1) 根据需要的配置初始化 UART。 (2) 向连接的设备发送或从其接收数据。 (3) 不再使用时反初始化 UART。
void sample_spi(void)
{
static struct tlsr_uart_cfg cfg = {
.buadrate = TLSR_UART_BDR_115200,
.data_bits = TLSR_UART_DATA_BITS_8,
.pairty = TLSR_UART_NO_PARITY,
.stop_bits = TLSR_UART_STOP_BIT_1,
.dma_en = 1,
};
int ret;
tlsr_uart_init(TLSR_UART_IDX_0, &cfg);
tlsr_uart_tx(TLSR_UART_IDX_0, tx_buf, tx_len, 1000);
tlsr_uart_rx(TLSR_UART_IDX_0, rx_buf, rx_len, 1000);
tlsr_uart_deinit(TLSR_UART_IDX_0);
}
注意事项
TBD
SPI(串行外设接口)
概述
TLSR9118的SPI支持主模式和从模式,可以与外部设备进行通信。它可以配置为标准SPI或四线SPI(QSPI),用于与兼容四线 SPI 的设备接口。
SPI主模式支持命令、地址和数据阶段,且数据可以在无命令和地址阶段的情况下传输。
要通过SPI从设接收或发送数据,对等的主设备必须在发送或接收实际数据之前,通过单一IO发送8位命令和8位虚拟数据。
API函数
-
初始化和配置:
tlsr_spi_init(): 初始化 SPI 模块。tlsr_spi_deinit(): 去初始化 SPI 模块。tlsr_spi_configure(): 设置 SPI 参数和选项。tlsr_spi_reset(): 重置 SPI 模块。
-
主模式操作:
- 数据传输:
tlsr_spi_master_tx(),tlsr_spi_master_tx_async() - 数据接收:
tlsr_spi_master_rx(),tlsr_spi_master_rx_async() - 综合传输和接收:
tlsr_spi_master_tx_rx(),tlsr_spi_master_tx_rx_async() - 基于命令的操作:
tlsr_spi_master_tx_with_cmd(),tlsr_spi_master_rx_with_cmd(), etc.
- 数据传输:
-
从模式操作:
- 缓冲区管理:
tlsr_spi_slave_set_tx_buf(),tlsr_spi_slave_set_rx_buf(),tlsr_spi_slave_set_tx_rx_buf() - 用户状态管理:
tlsr_spi_slave_set_user_state()
- 缓冲区管理:
开发指南
配置
struct tlsr_spi_cfg {
enum tlsr_spi_role role;
enum tlsr_spi_mode mode;
enum tlsr_spi_data_io_format data_io_format;
enum tlsr_spi_bit_order bit_order;
enum tlsr_spi_dummy_cycle slave_extra_dummy_cycle;
uint32_t master_cs_bitmap;
enum tlsr_spi_clk_src clk_src;
uint8_t clk_div_2mul;
uint8_t dma_en;
}
-
role: 选择主模式或从模式
-
mode: CPOL和CPHA模式
- TLSR_SPI_MODE_0: active high, 奇数边沿采样
- TLSR_SPI_MODE_1: active high, 偶数边沿采样
- TLSR_SPI_MODE_2: active low, 奇数边沿采样
- TLSR_SPI_MODE_3: active low, 偶数边沿采样
- data_io_format: 选择IO格式
- SPI_DATA_IO_FORMAT_SIGNLE
- SPI_DATA_IO_FORMAT_DUAL
- SPI_DATA_IO_FORMAT_QUAD
注意
- tlsr_spi_master_tx_rx 和 tlsr_spi_master_tx_rx_sync 不能在 DUAL 或 QUAD IO 格式下使用。
-
bit_order: 选择位顺序
- SPI_BIT_MSB_FIRST
- SPI_BIT_LSB_FIRST
-
slave_extra_dummy_cycle
- TLSR_SPI_DUMMY_CYCLE_NONE
- TLSR_SPI_DUMMY_CYCLE_SINGLE_IO_8
- TLSR_SPI_DUMMY_CYCLE_SINGLE_IO_16
- TLSR_SPI_DUMMY_CYCLE_SINGLE_IO_24
- TLSR_SPI_DUMMY_CYCLE_SINGLE_IO_32
- TLSR_SPI_DUMMY_CYCLE_DUAL_IO_4
- TLSR_SPI_DUMMY_CYCLE_DUAL_IO_8
- TLSR_SPI_DUMMY_CYCLE_DUAL_IO_12
- TLSR_SPI_DUMMY_CYCLE_DUAL_IO_16
- TLSR_SPI_DUMMY_CYCLE_QUAD_IO_2
- TLSR_SPI_DUMMY_CYCLE_QUAD_IO_4
- TLSR_SPI_DUMMY_CYCLE_QUAD_IO_6
- TLSR_SPI_DUMMY_CYCLE_QUAD_IO_8
-
master_cs_bitmap: Bitmap of GPIO numbers to be used for chip-selects
-
clk_src: 选择SPI IO 时钟源
- SPI_CLK_SRC_XTAL: XTAL 40Mhz
- SPI_CLK_SRC_PLL: PLL时钟。SPI0和SPI1使用240MHz PLL时钟,SPI2使用80MHz PLL时钟
-
clk_div_2mul: 源时钟分频值。将设置值乘以2后除以。例如: clk_src = SPI_CLK_SRC_XTAL and clk_div_2mul = 20, SPI IO时钟为1 MHz。
-
dma_en: 启用或禁用DMA传输
事件
-
主模式事件
- TLSR_SPI_EVENT_MASTER_TRANS_CMPL: SPI传输完成事件。此事件为异步API触发
-
从模式事件
- TLSR_SPI_EVENT_SLAVE_TX_REQUEST: 接收到读取命令时发生。
- TLSR_SPI_EVENT_SLAVE_RX_REQUEST: 接收到读取命令时发生。
- TLSR_SPI_EVENT_SLAVE_USER_CMD: 接收到未定义命令时发生。
- TLSR_SPI_EVENT_SLAVE_TRAN_CMPL: SPI传输完成事件。
主模式使用步骤:
- 步骤 1: 初始化SPI
- 步骤 2: 将SPI配置为主模式
- 步骤 3: 向从设备发送或从从设备接收数据
- 步骤 4: 在不再使用时去初始化 SPI
void sample_spi(void)
{
struct tlsr_spi_cfg cfg;
int ret;
memset(&cfg, 0, sizeof(cfg));
cfg.role = TLSR_SPI_ROLE_MASTER;
cfg.mode = TLSR_SPI_MODE_0;
cfg.data_io_format = TLSR_SPI_DATA_IO_FORMAT_SINGLE;
cfg.bit_order = TLSR_SPI_BIT_ORDER_MSB_FIRST;
cfg.clk_src = TLSR_SPI_CLK_SRC_XTAL,
cfg.clk_div_2mul = 20,
cfg.dma_en = 0,
tlsr_spi_init(tlsr_SPI_IDX_0);
tlsr_spi_configure(tlsr_SPI_IDX_0, &cfg, NULL, NULL);
tlsr_spi_master_tx(tlsr_SPI_IDX_0, tx_buf, tx_len, 1000);
tlsr_spi_master_rx(tlsr_SPI_IDX_0, rx_buf, rx_len, 1000);
tlsr_spi_master_tx_rx(tlsr_SPI_IDX_0, 0, tx_buf, tx_len, rx_buf, rx_len, 1000);
tlsr_spi_deinit(tlsr_SPI_IDX_0);
}
从模式使用步骤:
- 步骤 1: 初始化 SPI。
- 步骤 2: 将 SPI 配置为从模式。
- 步骤 3: 管理 Tx/Rx 缓冲区并响应事件。
- 步骤 4: 使用后去初始化 SPI。
int volatile g_spi_complete;
void spi_slave_complete_wait(void)
{
g_spi_complete = 0;
while (1) {
if (g_spi_complete) {
}
}
}
Int spi_slave_notify(struct tlsr_spi_event *event, void *ctx)
{
Switch (event->type) {
case TLSR_SPI_EVENT_SLAVE_TRANS_CMPL:
g_spi_complete = 1;
break;
default:
break;
}
return 0;
}
void sample_spi(void)
{
struct tlsr_spi_cfg cfg;
int ret;
memset(&cfg, 0, sizeof(cfg));
cfg.role = TLSR_SPI_ROLE_SLAVE;
cfg.mode = TLSR_SPI_MODE_0;
cfg.data_io_format = TLSR_SPI_DATA_IO_FORMAT_SINGLE;
cfg.bit_order = TLSR_SPI_BIT_ORDER_MSB_FIRST;
cfg.clk_src = TLSR_SPI_CLK_SRC_XTAL,
cfg.clk_div_2mul = 20,
cfg.dma_en = 0,
tlsr_spi_init(TLSR_SPI_IDX_0);
tlsr_spi_configure(TLSR_SPI_IDX_0, &cfg, spi_slave_notify, NULL);
tlsr_spi_slave_set_tx_buf(TLSR_SPI_IDX_0, tx_buf, tx_len);
tlsr_spi_slave_complete_wait();
tlsr_spi_slave_set_rx_buf(TLSR_SPI_IDX_0, rx_buf, rx_len);
tlsr_spi_slave_complete_wait();
tlsr_spi_slave_set_tx_rx_buf(TLSR_SPI_IDX_0, tx_buf, tx_len, rx_buf, rx_len);
tlsr_spi_slave_complete_wait();
tlsr_spi_deinit(TLSR_SPI_IDX_0);
}
注意事项
-
SPI实例: TLSR9118提供三个SPI实例。SPI0专用于闪存,不适用于其他用途。
-
DMA使用: 如果使用DMA,请确保传输和接收缓冲区位于允许DMA的区域内。
-
从模式考虑: 从模式中的通知功能应该快速执行,以避免由于主时钟延迟导致的事务失败。
-
支持多个从机模式(作为从机): SPI驱动应支持多个从机的连接模式,例如,通过为atcspi驱动启用CONFIG_SPI_SUPPORT_MULTI_SLAVES_AS_A_SLAVE配置项实现此功能。
- 主机必须在发送命令和数据之间插入足够的虚拟时钟周期(Dummy Cycles),以确保从机有足够的时间响应。
- 从机需要通过slave_extra_dummy_cycle参数配置虚拟时钟周期的数量,以匹配主机的设置。
- 虚拟时钟周期的数量与SPI总线的时钟频率直接相关,时钟越快,需要的虚拟时钟周期越多。
-
支持多个从机模式(作为主机): SPI驱动应支持多个从机连接模式,例如,通过为atcspi驱动启用CONFIG_SPI_SUPPORT_MULTI_SLAVES_AS_A_MASTER配置项实现此功能。
- 主机需要通过master_cs_bitmap参数提供用于选择从机的GPIO位图。
- 位图中每一位对应一个GPIO引脚,用于控制某一从机的片选信号。例如:如果需要使用GPIO0和GPIO15分别控制从机1和从机2,则应将master_cs_bitmap配置为 0x00008001。
I2C (互联集成电路)
功能描述
TLSR9118 SoC支持I2C的主模式和从模式,便于与各种外部设备进行通信。I2C接口旨在处理标准数据传输协议,确保与广泛的I2C兼容设备相容。
API函数
-
初始化和配置:
tlsr_i2c_init(): 初始化I2C模块。tlsr_i2c_deinit(): 去初始化I2C模块。tlsr_i2c_configure(): 配置I2C参数和选项。tlsr_i2c_reset(): 重置I2C模块。
-
主模式操作:
- 数据传输:
tlsr_i2c_master_tx(),tlsr_i2c_master_tx_async() - 数据接收:
tlsr_i2c_master_rx(),tlsr_i2c_master_rx_async() - 综合传输和接收:
tlsr_i2c_master_tx_rx(),tlsr_i2c_master_tx_rx_async() - 设备探测:
tlsr_i2c_master_probe()
- 数据传输:
-
从模式操作:
- 发送请求:
tlsr_i2c_slave_tx() - 接收请求:
tlsr_i2c_slave_rx()
- 发送请求:
开发指南
事件
- 主模式事件
- TLSR_I2C_EVENT_MASTER_TRANS_CMPL: 当主机完成传输时
- 从模式事件
- TLSR_I2C_EVENT_SLAVE_RX_REQUEST: 当从机接收到 RX(主机到从机)数据请求时
- TLSR_I2C_EVENT_SLAVE_TX_REQUEST: 当从机接收到 TX(从机到主机)数据请求时
- TLSR_I2C_EVENT_SLAVE_RX_CMPL: 当从机完成接收时
- TLSR_I2C_EVENT_SLAVE_TX_CMPL: 当从机完成传输时
主模式使用步骤:
- 步骤 1: 初始化I2C。
- 步骤 2: 将I2C配置为主模式。
- 步骤 3: 执行发送/接收操作。
- 步骤 4: 使用后去初始化 I2C。
static int i2c_master_notify(struct tlsr_i2c_event *event, void *ctx)
{
return 0;
} v
oid sample_i2c(void)
{
struct tlsr_i2c_cfg cfg;
int ret;
memset(&cfg, 0, sizeof(cfg));
cfg.role = TLSR_I2C_ROLE_MASTER;
cfg.master_clock = 400 * 1000;
cfg.pull_up_en = 1;
tlsr_i2c_init(TLSR_I2C_IDX_0);
tlsr_i2c_configure(TLSR_I2C_IDX_0, &cfg, i2c_master_notify, NULL);
tlsr_i2c_master_tx(TLSR_I2C_IDX_0, SLAVE_DEVICE_ADDR, tx_buf, tx_len, 1000);
tlsr_i2c_master_tx_rx(TLSR_I2C_IDX_0, SLAVE_DEVICE_ADDR,
tx_buf, tx_len, buf, len, 1000);
tlsr_i2c_master_rx(TLSR_I2C_IDX_0, SLAVE_DEVICE_ADDR, buf, len, 1000);
tlsr_i2c_deinit(TLSR_I2C_IDX_0);
}
从模式使用步骤:
- 步骤 1: 初始化 I2C
- 步骤 2: 将 I2C 配置为从模式。
- 步骤 3: 当 I2C 通知函数被调用时,准备 Tx 或 Rx 数据
- 步骤 4: 使用后去初始化 I2C
示例:
static int i2c_slave_notify(struct tlsr_i2c_event *event, void *ctx)
{
switch (event->type) {
case TLSR_I2C_EVENT_SLAVE_TX_REQUEST:
tlsr_i2c_slave_tx(TLSR_I2C_IDX_1, tx_buf, tx_len);
break;
case tlsr_I2C_EVENT_SLAVE_RX_REQUEST:
tlsr_i2c_slave_rx(TLSR_I2C_IDX_1, rx_buf, rx_len);
break;
case TLSR_I2C_EVENT_SLAVE_TX_CMPL:
len = event->data.slave_tx_cmpl.len;
/* do something about tx data */
break;
case TLSR_I2C_EVENT_SLAVE_RX_CMPL:
len = event->data.slave_rx_cmpl.len;
/* do something about rx data */
break;
default:
break;
}
return 0;
}
void sample_i2c(void)
{
struct tlsr_i2c_cfg cfg;
int ret;
memset(&cfg, 0, sizeof(cfg));
cfg.role = TLSR_I2C_ROLE_SLAVE;
cfg.pull_up_en = 1;
tlsr_i2c_init(TLSR_I2C_IDX_0);
tlsr_i2c_configure(TLSR_I2C_IDX_1, &cfg, i2c_slave_notify, NULL);
...
tlsr_i2c_deinit(TLSR_I2C_IDX_1);
}
注意事项
对于从模式,如果用户提供的缓冲区不足,
- 当主机尝试读取超出缓冲区的内容时,将返回 0 给主机
- 当主机尝试写入超出缓冲区的内容时,数据将被静默丢弃。
计时器
概述
TLSR9118 SoC 配备了强大的计时器功能,提供两个硬件计时器。每个计时器具有四个通道,可以配置成各种模式,这种灵活性允许创建四个独立的计时器,以满足不同的应用需求。
功能描述
- tlsr_timer_configure: 配置计时器模式
- tlsr_timer_start: 启动计时器
- tlsr_timer_stop: 停止计时器
- tlsr_timer_start_multi: 同时启动多个计时器
- tlsr_timer_stop_multi: 同时停止多个计时器
- tlsr_timer_value: 如果计时器配置为自由运行模式,读取计时器的值
开发指南
执行以下步骤:
- 步骤 1: 配置计时器
- 步骤 2: 启动计时器
- 步骤 3: 读取计时器
- 步骤 4: 停止计时器
示例:
static uint8_t timer_id[3] = { 0, 1, 2};
static int timer_notify(uint32_t pin, void *ctx)
{
uint8_t timer_id = *((uint8_t *)ctx);
/* differentiate the notification based on the context provided */
return 0;
} v
oid sample_timer(void)
{
struct tlsr_timer_cfg cfg0, cfg1, cfg2;
uint32_t value;
cfg0.mode = TLSR_TIMER_MODE_PERIODIC;
cfg0.intr_en = 1;
cfg0.data.periodic.duration = 1000000;
cfg1.mode = TLSR_TIMER_MODE_ONESHOT;
cfg1.intr_en = 1;
cfg1.data.oneshot.duration = 3000000;
cfg2.mode = TLSR_TIMER_MODE_FREERUN;
cfg2.data.freerun.freq = 1000000;
tlsr_timer_configure(TLSR_TIMER_IDX_0, TLSR_TIMER_CH_0, timer_notify, &cfg0,
&timer_id[0]);
tlsr_timer_configure(TLSR_TIMER_IDX_1, TLSR_TIMER_CH_1, timer_notify, &cfg1,
&timer_id[1]);
tlsr_timer_start(TLSR_TIMER_IDX_0, TLSR_TIMER_CH_0);
tlsr_timer_start(TLSR_TIMER_IDX_0, TLSR_TIMER_CH_1);
...
tlsr_timer_value(TLSR_TIMER_IDX_0, TLSR_TIMER_CH_2, &value);
...
tlsr_timer_stop_multi(TLSR_TIMER_IDX_0, 1 << TLSR_TIMER_CH_0 | 1 <<
TLSR_TIMER_CH_1);
}
注意事项
TIMER1实例被电源管理和BLE子系统使用。当启用电源管理(PM)或BLE时,应用程序不应使用索引TLSR_TIMER_IDX_1调用计时器API。
GPIO(通用输入/输出)
概述
TLSR9118 SoC配备了25个通用输入/输出(GPIO)引脚。这些GPIO可以配置为输入或输出,为与外部硬件和传感器的接口提供多功能性。
API函数
- 配置和控制:
tlsr_gpio_configure(): 配置GPIO引脚为输入或输出,包括上拉或下拉电阻的选项。tlsr_gpio_write(): 设置GPIO引脚的输出电平。tlsr_gpio_read(): 读取GPIO引脚的输入电平。tlsr_gpio_enable_interrupt(): 启用GPIO中断,支持事件驱动编程。tlsr_gpio_disable_interrupt(): 禁用GPIO中断。
开发指南
GPIO 使用的基本步骤:
- 步骤 1: 为预期的输入/输出功能配置 GPIO 引脚。
- 步骤 2: 对于输出使用,向 GPIO 引脚写入期望的电平。
- 步骤 3: 对于输入使用,根据需要从 GPIO 引脚读取电平。
- 步骤 4: 如果使用中断,启用它们并提供一个回调函数来处理事件。
示例代码:
static int tlsr_cli_gpio_notify(uint32_t pin, void *ctx)
{
gpio_stats[pin]++;
return 0;
} v
oid sample_gpio(void)
{
/* set PIN 6 as gpio output */
tlsr_gpio_configure(6, TLSR_GPIO_PROP_OUTPUT);
tlsr_gpio_write(6, 1);
/* set PIN 7 as gpio input, and enable interrupt */
tlsr_gpio_configure(7, TLSR_GPIO_PROP_INPUT_PULL_DOWN);
tlsr_gpio_enable_interrupt(7, TLSR_GPIO_INT_BOTH_EDGE,
tlsr_cli_gpio_notify, NULL);
}
注意事项
- 电源域: GPIO引脚0到7位于“常开”电源域,这意味着它们可以在低功耗模式下保持状态并接收唤醒事件。
- 中断处理:应注意确保中断回调函数高效,并且不会阻塞关键任务。
eFuse
功能描述
TLSR9118的eFuse(电子可编程保险丝)提供1024位的非易失性内存。这个功能通常用于存储系统关键数据,如安全引导密钥、设备身份和RF校准数据。
API函数
- eFuse操作:
tlsr_efuse_read(): 从指定的eFuse地址读取数据。tlsr_efuse_write(): 向指定的eFuse地址写入数据。
开发指南
保留字段及其大小如下所示。
#define TLSR_EFUSE_ADDR_ROOT_KEY 0
#define TLSR_EFUSE_SIZE_ROOT_KEY 128
#define TLSR_EFUSE_ADDR_PARITY 128
#define TLSR_EFUSE_SIZE_PARITY 1
#define TLSR_EFUSE_ADDR_HARD_KEY 129
#define TLSR_EFUSE_SIZE_HARD_KEY 1
#define TLSR_EFUSE_ADDR_FLASH_PROT 130
#define TLSR_EFUSE_SIZE_FLASh_PROT 1
#define TLSR_EFUSE_ADDR_SECURE_BOOT 131
#define TLSR_EFUSE_SIZE_SECURE_BOOT 1
#define TLSR_EFUSE_ADDR_AR_BL_EN 132
#define TLSR_EFUSE_SIZE_AR_BL_EN 1
#define TLSR_EFUSE_ADDR_SDIO_OCR_EN 132
#define TLSR_EFUSE_SIZE_SDIO_OCR_EN 1
#define TLSR_EFUSE_ADDR_AR_FW_EN 133
#define TLSR_EFUSE_SIZE_AR_FW_EN 1
#define TLSR_EFUSE_ADDR_CUST_ID 160
#define TLSR_EFUSE_SIZE_CUST_ID 8
#define TLSR_EFUSE_ADDR_CHIP_ID 192
#define TLSR_EFUSE_SIZE_CHIP_ID 32
#define TLSR_EFUSE_ADDR_PK_HASH 224
#define TLSR_EFUSE_SIZE_PK_HASH 256
#define TLSR_EFUSE_ADDR_SDIO_OCR 544
#define TLSR_EFUSE_SIZE_SDIO_OCR 20
#define TLSR_EFUSE_ADDR_WLAN_MAC_ADDR 576
#define TLSR_EFUSE_SIZE_WLAN_MAC_ADDR 48
#define TLSR_EFUSE_ADDR_BLE_MAC_ADDR 624
#define TLSR_EFUSE_SIZE_BLE_MAC_ADDR 48
#define TLSR_EFUSE_ADDR_RF_CAL 672
#define TLSR_EFUSE_SIZE_RF_CAL 64
#define TLSR_EFUSE_ADDR_AL_BL_VER 736
#define TLSR_EFUSE_SIZE_AL_BL_VER 64
#define TLSR_EFUSE_ADDR_RESERVED 800
#define TLSR_EFUSE_SIZE_RESERVED 224
示例:
uint8_t mac[8];
uint8_t custom_data[2];
void sample_efuse(void)
{
tlsr_efuse_read(TLSR_EFUSE_ADDR_WLAN_MAC_ADDR,
TLSR_EFUSE_SIZE_WLAN_MAC_ADDR,
mac);
tlsr_efuse_write(800, 16, &custom_data);
}
注意事项
由于eFuse只能写入一次,因此在写入新值时需要格外小心。eFuse的每一位只能从0变成1,不能反向变化。在特殊情况下,同一字段可以多次写入以将特定位设置为 1。
ADC
功能说明
TLSR9118 SoC配备了一个模拟数字转换器 (ADC),它能够读取模拟电压水平并将其转换成数字格式。下表展示了物理GPIO引脚与对应 ADC 通道之间的对应关系。
| ADC通道 | GPIO引脚 |
|---|---|
| 4 | 4 |
| 5 | 7 |
| 6 | 0 |
| 7 | 1 |
API功能
- ADC测量
- tlsr_adc_read(), tlsr_adc_read_aync():从指定 ADC 通道读取数据。
- tlsr_adc_reset():重置 ADC。
开发指南
示例代码:
static int adc_notify(void *ctx)
{
return 0;
} u
int16 ch0_buf[8];
uint16 ch1_buf[16];
void sample_efuse(void)
{
tlsr_adc_read(TLSR_ADC_SINGLE_CH_0, ch0_buf, 8);
tlsr_adc_read_async(TLSR_ADC_SINGLE_CH_1, ch1_buf, 16, adc_notify, NULL);
}
这个示例展示了如何执行同步和异步ADC读取。 tlsr_adc_read 函数用于从通道0进行同步读取,而tlsr_adc_read_async用于从通道1进行异步读取,配合回调函数使用。
注意事项
TBD
I2S
概述
TLSR9118的I2S支持主从模式,可以与外部音频CODEC交换音频样本数据。它支持多种格式和字长,具体描述如下。
API函数
初始化和配置:
tlsr_i2s_init(): 初始化I2S模块。tlsr_i2s_deinit(): 关闭I2S模块。tlsr_i2s_configure(): 设置I2S参数和选项。tlsr_i2s_start(): 启动指定方向的I2S数据流。tlsr_i2s_stop(): 停止指定方向的I2S数据流。tlsr_i2s_read_block(): 从I2S输入流中读取一个音频样本块,返回成功或错误代码。tlsr_i2s_write_block(): 向I2S输出流写入一个音频样本块,返回成功或错误代码。tlsr_i2s_get_block_buffer_size(): 返回配置的块缓冲区大小。
开发指南
配置
struct tlsr_i2s_cfg {
enum tlsr_i2s_wl word_length; // 每个音频字(通道)的位数
enum tlsr_i2s_fmt format; // I/O格式
enum tlsr_i2s_role role; // 总线角色(主/从)
enum tlsr_i2s_direction dir; // 数据传输方向(Tx/Rx)
uint32_t fs; // 音频采样频率(Hz)
int duration_per_block; // 每个缓冲区块的时间持续(毫秒)
int number_of_blocks; // 要分配的最大缓冲区块数
int timeout; // 读/写超时(毫秒)
}
- word_length:
- TLSR_I2S_WL_16: 16位
- TLSR_I2S_WL_20: 20位
- TLSR_I2S_WL_24: 24位
注意
- 20位或24位的字长在内存中将占用32位。
-
format: 选择I/O格式
- TLSR_I2S_FMT_I2S
- TLSR_I2S_FMT_LJ
- TLSR_I2S_FMT_RJ
-
role: 选择总线角色
- TLSR_I2S_ROLE_MASTER: 生成I2S位时钟和字时钟。
- TLSR_I2S_ROLE_SLAVE
-
dir: 指定数据传输方向
- TLSR_I2S_RX
- TLSR_I2S_TX
-
fs: 音频采样频率(Hz)
-
duration_per_block: 每个缓冲区块对应的时间持续(毫秒)
注意
- 必须小于1000(1秒)。
-
number_of_blocks: 要分配的最大缓冲区块数
-
timeout: 读/写超时(毫秒)
传输使用步骤:
(1) 初始化I2S。
(2) 配置I2S以支持Tx和Rx方向。
(3) 在Tx方向启动I2S流。
(4) 向I2S Tx流写入音频样本块。
(5) 停止Tx流并在不再需要时停止I2S模块。
void sample_i2s_write(uint32 pattern)
{
struct tlsr_i2s_cfg cfg;
int bufsz;
uint8_t *buf;
tlsr_i2s_init();
memset(&cfg, 0, sizeof(cfg));
cfg.word_length = TLSR_I2S_WL_16;
cfg.format = TLSR_I2S_FMT_I2S;
cfg.role = TLSR_I2S_ROLE_MASTER;
cfg.fs = 44100;
cfg.duration_per_block = 100;
cfg.number_of_blocks = 5;
cfg.timeout = 3000;
cfg.dir = TLSR_I2S_RX;
tlsr_i2s_configure(&cfg);
cfg.dir = TLSR_I2S_TX;
tlsr_i2s_configure(&cfg);
bufsz = tlsr_i2s_get_block_buffer_size(&cfg);
buf = zalloc(bufsz);
for (int i = 0; i < bufsz / 4; i = i + 4) {
memcpy(buf + i, &pattern, sizeof(uint32_t));
}
tlsr_i2s_start(TLSR_I2S_TX);
for (int i = 0; i < cfg.number_of_blocks; i++) {
tlsr_i2s_write_block(buf, bufsz);
}
tlsr_i2s_stop(TLSR_I2S_TX);
free(buf);
tlsr_i2s_deinit();
}
接收的使用步骤:
(1) 初始化I2S。
(2) 配置I2S以支持Tx和Rx方向。
(3) 在Rx方向启动I2S流。
(4) 从I2S Rx流中读取音频样本块。
(5) 停止Rx流并在不再需要时停止I2S模块。
void sample_i2s_read(void)
{
struct tlsr_i2s_cfg cfg;
int bufsz, len;
uint8_t *buf;
tlsr_i2s_init();
memset(&cfg, 0, sizeof(cfg));
cfg.word_length = TLSR_I2S_WL_16;
cfg.format = TLSR_I2S_FMT_I2S;
cfg.role = TLSR_I2S_ROLE_MASTER;
cfg.fs = 44100;
cfg.duration_per_block = 100;
cfg.number_of_blocks = 5;
cfg.timeout = 3000;
cfg.dir = TLSR_I2S_RX;
tlsr_i2s_configure(&cfg);
cfg.dir = TLSR_I2S_TX;
tlsr_i2s_configure(&cfg);
bufsz = tlsr_i2s_get_block_buffer_size(&cfg);
buf = zalloc(bufsz);
tlsr_i2s_start(TLSR_I2S_RX);
while (1) {
ret = tlsr_i2s_read_block(buf, &len);
if (ret) {
if (ret == WITS_ERR_NO_MEM) {
printf(“I2S is not running.\n”);
}
Break;
}
/* Do something with buf */
}
tlsr_i2s_stop(TLSR_I2S_RX);
free(buf);
tlsr_i2s_deinit();
}
注意事项
-
内存使用:TLSR9118的I2S流处理可用的内存有限。请根据需要调整cfg.duration_per_block和cfg.number_of_blocks,以防止内存溢出。
-
配置:无论实际数据传输方向如何,必须配置I2S的Tx和Rx方向。确保两个方向的参数相同。
SDK外设示例
引言
示例
SDK外设示例描述了如何构建和运行SDK中提供的示例应用程序。
SDK包含各种类型的示例应用程序。要探索可用的示例,请导航至[SDK]/api/examples 目录。
目录结构如下:

参考SDK入门指南准备构建环境。
构建和配置
要构建特定的示例应用程序,请按照以下步骤操作:
- 从SDK的根目录,使用以下命令配置基本选项:
$ make tlsr9xxxs_defconfig
- 要更改配置或选择示例应用程序,请使用以下命令:
$ make menuconfig
- 导航至
Applications --->

- 选择
Applications (Command-line demo) --->

- 选择所需的示例。

- 如果有多个应用程序,将出现一个子菜单。

- 从子菜单中选择所需的示例。

- 使用以下命令构建固件:
$ make
构建成功完成后,将生成wits.mcuboot.bin文件,可以加载到开发板上。
应用程序入口点
名为"init"的第一个OS线程在线程上下文中调用main()函数。
Wits SDK 将默认main()定义为一个弱函数,它什么都不做,只返回0。如果应用程序定义了一个 main()函数,那么将调用它。例如,api/examples/peripherals/i2c_eeprom/main.c定义了主函数,它是I2C示例的主入口点。如果配置了I2C示例,固件将在启动后运行此函数。

一些示例有自己的主函数,而另一些则没有。在任何一种情况下,大多数示例都依赖于用户命令进行测试。
当使用控制台命令行接口 (CLI) 时, "init"线程还处理命令输入并执行相应的函数。因此,main()函数不应阻塞并应退出以允许处理命令输入。
重建
尝试不同的示例时,应注意不要引起PINMUX设置的冲突。建议通过运行清理命令从干净的状态开始配置。
$ make distclean
外设示例
LED控制 – 闪烁
此示例展示了如何控制和切换GPIO,使连接的LED能够开启或关闭。
板子设置
- 将LED连接到GPIO16
构建配置
- 该示例可以从应用程序菜单中选择。

示例
- 启动后,GPIO被配置为输出。
- 使用
osDelay, GPIO在持续时间内切换。 - 过程重复配置的次数。

执行此示例时,控制台日志如下所示。

LED控制 - PWM
此示例展示了如何控制定时器的PWM功能以控制LED。
板子设置
- 将LED连接到GPIO15
构建配置
- 可以从应用程序菜单中选择该示例。

示例

启动后,根据配置的持续时间生成PWM信号。

I2C
此示例展示了I2C主机和从机的特性。
此示例中使用了两个I2C。I2C0用作主机,I2C1用作从机。此示例模拟了典型的EEPROM。
板子设置
- 要运行此示例,必须使用跳线设置板子。进行以下连接:
- GPIO15和GPIO17
- GPIO16和GPIO18
构建配置
- 可以从应用程序菜单中选择该示例。

示例
- 示例代码实现了初始化I2C主机和从机。一旦它们都初始化了,就可以接受用户命令并执行所需的操作。



- 启动后,使用 "eeprom" 命令进行测试。示例控制台日志如下所示。用户可以尝试在不同的 EEPROM地址处写入和读取不同长度的数据。

SPI
此示例展示了通用的SPI传输。
此示例中使用了两个SPI。SPI1用作主机,SPI2用作从机。
板子设置
- 要运行此示例,必须使用跳线设置板子。进行以下连接:
- GPIO15 和 GPIO2
- GPIO16 和 GPIO3
- GPIO17 和 GPIO4
- GPIO18 和 GPIO5
- GPIO19 和 GPIO6
- GPIO20 和 GPIO7
构建配置
- 可以从应用程序菜单中选择该示例。

示例
- 使用 "spi_tr" 命令,可以测试带有单IO、双IO或四IO的SPI传输。该命令将从主设备传输预定义的消息到从设备。

ADC
此示例展示了如何从GPIO引脚读取ADC值。
板子配置
- 将不同电平的信号连接到 GPIO:
- 通道 4: GPIO4
- 通道 5: GPIO7
- 通道 6: GPIO0
- 通道 7: GPIO1
构建配置
- 可以从应用程序菜单中选择该示例。

示例
- 使用 "adc" 命令读取一个通道所需的次数。更改输入电平后再次尝试。

低功耗开发指南
简介
低功耗开发指南描述了如何使用电源管理API来实现低功耗应用。
低功耗状态
设备的内部电源管理单元(PMU)提供了多种节电模式。但并不是所有的 PMU 模式都适用于实际使用场景。相反,建议用户使用 SDK 中提供的 PM API。
SDK 支持以下 PM 状态:
- Active 活跃状态
- Light Sleep 浅睡模式
- Deep Sleep 深睡模式
- Hibernation 超深睡模式
在活跃状态下,SoC的每个组件都保持供电和运行。相反,超深睡模式是最节能的,其中大部分组件都关闭电源,除了始终开启(AON)电源域中的那些组件。
状态转换
在SDK中启用电源管理后,设备会自动在不同功耗模式之间转换以优化功耗。
每个功耗模式都需要特定的保存和/或恢复操作,以确保在进入低功耗状态之前和之后的上下文保持一致。更节能的状态涉及更广泛的操作,因此需要更多的时间。
设备的下一个功耗模式由其空闲周期决定,即直到下一个预定任务的时间间隔。每个PM状态的时间条件如下所示,但可以根据应用需求进行调整。
| 功耗模式 | 预期空闲周期 |
|---|---|
| 活跃模式 | - |
| 浅睡模式 | > 550 微妙 |
| 深睡模式 | > 10.2 毫秒 |
| 超深睡模式 | > 1.7 秒 |
当设备切换到功耗模式(除活跃模式外)时,它在唤醒事件发生后将始终返回到活跃模式。不支持两个不同功耗模式之间的直接转换。
转换图示:

电源管理条件
通常,转换到低功耗状态取决于以下因素:
- 操作系统有足够的空闲时间,这意味着所有的线程都处于空闲状态并等待事件。
- 外设驱动程序没有禁用电源管理。
- 用户应用程序启用了电源管理和特定的电源管理状态。
网络保活
在所有低功耗状态下都保持网络连接。
在深睡模式和超深睡模式中,专门的软件操作用于监视 WiFi 活动,并在必要时引起唤醒事件。此软件从 RAM 运行,无需使用闪存和其他高功耗组件。这种方法确保了 WiFi 连接,同时最大限度地减少了设备的总体功耗。
唤醒源
当设备处于低功耗状态时,以下外设可以用作唤醒源:
- RTC
- GPIO
- UART
- SDIO
- USB
唤醒事件并不总是可用的,它们取决于特定的电源管理状态。RTC和GPIO可以在所有低功耗状态中用来唤醒设备。
有关使用UART、SDIO、USB唤醒的详细信息,请咨询Telink。
RTC
RTC(实时时钟)可以设置为在指定的未来时间提示设备唤醒。这种配置通常由操作系统管理,确保操作系统线程在正确的间隔内操作。
用户不需要深入了解电源管理操作来利用这一点。他们可以使用标准的操作系统API,如延迟函数或进程间通信函数。在后台,操作系统收集线程相关数据,将 RTC 调整到最近的所需唤醒时间,并在所有线程都处于空闲状态时转换到低功耗状态。
建议在电源管理处于活动状态时避免直接访问RTC API。
GPIO
GPIO(通用输入/输出)可以在所有电源管理状态下触发唤醒事件。
要指定GPIO作为唤醒源,其引脚应设置为输入,并应激活相关的中断。配置上拉或下拉设置也是有益的。随后,应用程序必须通知电源管理模块将其识别为唤醒源。
应该强调的是,只有AON(始终开启)域中的GPIO具有唤醒功能的能力。特别指出的是,这些GPIO从GPIO0到GPIO7。
电源管理状态
活跃状态
在此状态下,SoC(系统芯片)完全运行。所有外设都已上电并准备使用。
浅睡模式
在浅睡模式状态下:
- 处理器和外设都是时钟门控的。
- 所有寄存器内容和内存都被保留。
- 定时器计数保持不变。
软件的职责包括:
- 进入此状态时保存系统时间。
- 退出时恢复系统时间,考虑到在此状态中所花费的时间。
深睡模式
在深睡模式状态下:
- 处理器和外设都是电源门控的。
- XTAL(晶体振荡器)和 PLL(锁相环)都被关闭。
- 处理器和外设的寄存器不被保留。
软件的职责包括:
- 进入此状态时保存系统时间并挂起外设设备。
- 退出时恢复系统时间并恢复外设设备。
超深睡模式
在超深睡模式期间:
- 处理器、外设、 XTAL 和 PLL 都是电源门控的。
- 额外的处理器 LDO(低压差稳压器)和 DCDC(直流-直流转换器)被停用。
- 所有内存部分,除了 AON(始终开启) SRAM 中的部分,都被断电。
软件任务包括:
- 进入超深睡模式时保存系统时间、挂起外设设备并将内存内容存储到闪存中。
- 离开此低功耗状态时, 唤醒时恢复系统时间、恢复外设设备并从闪存中检索内存内容。
深睡模式存在一个限制。考虑到某些上下文存储在闪存中,闪存的擦除和写入周期是有限的。考虑到SoC集成闪存的特性,建议将超深睡模式的发生次数限制为每天最多 27 次。
API接口
前提条件
为了启用电源管理,用户应从menuconfig中配置PM选项。

要使用PM API接口,用户还应从menuconfig中启用PM API选项。

以下 API 可以用于客制化电源管理模块的行为:
- tlsr_pm_power_down
- tlsr_pm_enable_wakeup_io
- tlsr_pm_disable_wakeup_io
- tlsr_pm_enable_lowpower
- tlsr_pm_disable_lowpower
- tlsr_pm_disable_lowpower_timeout
- tlsr_pm_enable_state
- tlsr_pm_disable_state
- tlsr_pm_register_handler
- tlsr_pm_unregister_handler
关机(带持续时间)
函数:
- int tlsr_pm_power_down(uint32_t duration)
The tlsr_pm_power_down 可以用于使设备无条件地进入超深睡模式。所有网络连接都会丢失。
如果 duration 设置为非零值,设备将在指定时间后唤醒。零持续时间意味着只有 GPIO 事件可以唤醒设备。唤醒后,设备初始化,就像经历电源上电复位。
GPIO唤醒控制
函数:
- int tlsr_pm_enable_wakeup_io(uint8_t pin)
- int tlsr_pm_disable_wakeup_io(uint8_t pin)
在AON域中总共有 8 个 GPIO(从 GPIO0 到 GPIO7)。每个GPIO都可以被指定为唤醒引脚。但是,如果不调用 tlsr_pm_enable_wakeup,即使将 GPIO 设置为输入,也不会启动唤醒事件。
低功耗状态控制
函数:
- int tlsr_pm_enable_lowpower(void)
- int tlsr_pm_disable_lowpower(void)
这些函数允许应用程序控制 PM 操作。如果应用程序需要为关键任务临时禁用 PM,它应该调用 tlsr_pm_disable_lowpower。任务结束并需要重新激活 PM 时,应调用 tlsr_pm_enable_lowpower。
Low Power State Control
Functions:
- int tlsr_pm_enable_state(enum telink_pm_state state)
- int tlsr_pm_disable_state(enum telink_pm_state state)
由于支持不同的PM状态,应用程序可以启用或禁用每个特定的PM状态。当禁用特定状态时,设备在空闲期间将转换到下一个可用的PM状态。
如果所有状态都被关闭,那么设备将不会进入任何低功耗状态。
电源管理状态变化通知
原型:
- typedef void (*tlsr_pm_notify)(enum telink_pm_state state);
函数:
- int tlsr_pm_register_handler(telink_pm_notify callback)
- int tlsr_pm_unregister_handler(telink_pm_notify callback)
应用程序可以得到 PM 状态变化的通知。它应该调用 tlsr_pm_register_handler 来注册要调用的特定于应用程序的函数。
可以通过多次使用不同的回调调用 tlsr_pm_register_handler 来注册多个回调。
当进入低功耗状态时,回调将被调用,状态参数是:TLSR_PM_STATE_LIGHT_SLEEP、 TLSR_PM_STATE_DEEP_SLEEP或TLSR_PM_STATE_HIBERNATION之一。
从低功耗状态唤醒时,回调将被调用,状态参数始终为 TLSR_PM_STATE_ACTIVE。
例如,这些回调函数可以用于在进入低功耗状态之前执行特定于应用程序的清理操作,如关闭LED、禁用传感器等。
注意
- 这些回调函数是从操作系统的空闲任务中调用的,因此应用程序代码的大小受限于空闲任务的堆栈大小。此外,所有的中断都被禁用了。回调函数不应调用任何操作系统 API 或与中断相关的函数。建议该函数尽快完成。
SDK电源管理示例
引言
示例
SDK电源管理示例描述了如何构建和运行SDK提供的电源管理。此示例旨在测试PM(电源管理)功能的各种配置和特性。
有关更多信息,请参考以下章节:
-
SDK入门指南,用于设置构建环境。
-
SDK示例外设,用于构建示例。
-
低功耗开发指南, 用于电源管理。
-
Wi-Fi软件开发指南,用于Wi-Fi。
电源管理命令行
SDK提供了CLI命令来配置和连接到Wi-Fi网络,以及监控状态和统计信息。
在解释示例之前,展示如何使用CLI是有帮助的。下一节将展示如何通过源代码自动完成相同的过程。
构建和配置
按照以下步骤构建示例应用程序:
$ make tlsr9xxxs_defconfig
$ make
构建成功完成后,将生成wits.mcuboot.bin,可以加载到开发板上。
命令
在控制台中,使用以下命令连接到WiFi网络。用户需要根据他们的Wi-Fi AP环境配置参数:
(1) 注册事件回调以接收事件:
$ wifi reg_evt_cb
(2) 启动站点模式:
$ wifi sta_start
(3) 配置站点参数、认证类型、SSID、密钥:
$ wifi sta_cfg Redmi_Test 2 12345678 00:00:00:00:00:00 1 0
(4) 连接到AP:
$ wifi sta_connect
尝试使用wifi help获取可用选项和命令描述。

默认情况下,PM(电源管理)是禁用的,可以使用以下命令启用:
(1) 启用Wi-Fi节能模式:
$ wifi sta_set_ps 1
(2) 注册PM事件回调:
$ pm reg_cb
(3) 启用全局PM:
$ pm enable

尝试使用pm获取可用选项和命令描述。
电源管理示例
上一节展示了如何使用CLI控制设备。在本节中,一个示例展示了如何使用源代码实现相同的功能。
构建和配置
按照以下步骤构建低功耗示例应用程序:
$ make tlsr9xxxs_defconfig
$ make menuconfig
select Applications -> Power Save Demo
$ make

在构建固件之前,必须根据测试环境更改AP信息。SSID、认证类型和密钥应与要连接的AP的信息匹配。
源代码更改
根据以下配置更改源代码:

构建成功完成后,将生成wits.mcuboot.bin,可以加载到开发板上。
示例
启动后,使用以下命令连接到Wi-Fi并启用低功耗模式:
$ lowpower 1
设备将尝试连接到Wi-Fi网络并进入低功耗状态。默认情况下,示例将配置监听间隔为1000毫秒。

设备固件更新指南
引言
TLSR9118 SDK支持空中设备固件更新(DFU OTA)。本指南为您提供了如何利用网络连接进行固件更新的详细说明。
在SDK中,我们已集成了广泛使用的开源引导加载程序MCUBoot。此外,我们还提供了专门针对特定架构的移植层,以便于无缝集成。
需要特别注意的是, MCUBoot专为支持完整的固件更新而设计,不支持增量更新。为了更全面地了解 MCUBoot及其功能,建议用户查阅其官方文档,地址为:https://docs.mcuboot.com/.
MCUBoot提供了多种配置选项,以满足不同应用的需求。其中一些选项可以通过menuconfig 进行访问和修改,而其他选项则需要在mcboot_config.h 文件中手动调整。
工作流程
高级操作
设备固件的更新过程可以总结为以下几个步骤:
- 初始设置: 设备预先加载有引导加载程序和主应用程序。
- 启动过程:开机时,引导加载程序启动位于主应用程序分区的程序。
- 更新准备:在主应用程序中,更新代理负责下载新的应用程序固件。 这个新固件存储在一个辅助应用程序分区中。
- 固件下载完成:一旦新的应用程序固件成功下载, 应用程序将记录此状态并触发系统重启。
- 引导加载程序检查:在随后的启动过程中,引导加载程序检查记录的状态。如果在辅助应用程序分区中检测到更新的固件,引导加载程序将与主应用程序分区中的固件交换。
- 应用程序启动:然后,引导加载程序从主应用程序分区启动程序, 该分区现在包含更新的固件。
- 状态清除:如果新的应用程序固件正常运行,应用程序可以继续清除更新状态,表示成功更新。

对于OTA固件更新过程,设备必须为辅助应用程序分区分配与主应用程序分区相同大小的空间。此外,还需要一个指定的临时区,作为在更新过程中交换主应用程序分区和辅助应用程序分区固件的临时空间。
闪存布局
为了成功地更新固件,设备需要在闪存中指定特定的区域。这些区域包括:
- 引导加载程序
- 主应用程序分区
- 辅助应用程序分区
- 临时区
以下是TLSR9118内部16Mbit闪存的推荐布局:
| 组件 | 地址 | 大小(KB) |
|---|---|---|
| 引导程序 | 0x80000000 | 64 |
| 临时区 | 0x80010000 | 16 |
| 存储文件系统 | 0x80014000 | 16 |
| 休眠备份 | 0x80018000 | 416 |
| 主应用程序分区 | 0x80080000 | 768 |
| 辅助应用程序分区 | 0x80140000 | 768 |
您可以使用menuconfig过程修改闪存布局的地址和大小。如果对闪存布局进行了任何更改,都必须重新构建引导加载程序和应用程序,以识别这些修改。


此外,由于闪存擦除块的大小,地址必须与4KB边界对齐。
构建固件
配置与构建
SDK中的默认配置已经启动了固件更新功能。因此,生成的应用固件将包括MCUBoot头部,使其适用于更新。
以下是使能固件更新功能的 menuconfig 选项的示例:

当设备固件更新被启用时,典型的输出名为wits.mcuboot.bin。
固件格式
MCUboot要求一个提供关于固件详细信息的镜像头部。一个固定大小的头部总是附加在可执行固件的前面,如果需要更多信息,则会附加可变大小的TLV格式数据。在构建过程中,imgtool工具会附加这些额外的数据。
头部包括以下细节:
- 魔法数字
- 加载内存地址
- 头部大小
- TLV 大小
- 固件大小
- 标志位
- 版本
尾部可能包含以下信息:
- 公钥或密钥哈希
- 固件哈希
- 签名
源代码概览
主要的MCUboot源文件
以下是一些重要的MCUBoot源代码。
| 源代码 | 描述 |
|---|---|
| lib/tlsr_mcuboot/mcuboot | 原始MCUboot库代码 |
| lib/tlsr_mcuboot/mcuboot/wits | 用于SDK的Telink移植层 |
| lib/tlsr_mcuboot/loader | MCUboot引导程序主函数 |
| lib/tlsr_mcuboot/update_agent | MCUboot更新代理示例 |
API函数
以下函数对于引导程序和应用程序都是不可或缺的:
| 名称 | 描述 |
|---|---|
| boot_go | 引导程序调用它来启动应用程序的引导。过程将根据 状态数据包括验证、交换和引导。 |
| boot_set_pending_multi | 应用程序调用它来标记镜像处于挂起状态,以便引导 程序在下次重启时交换并引导镜像。 |
| boot_set_confirmed_multi | 应用程序调用它来标记当前镜像有效并永久设置。 |
| flash_area_open | 根据给定的标识从闪存映射中检索闪存区域。 |
| flash_area_read | 将闪存内容读取到缓冲区。 |
| flash_area_write | 将缓冲区写入闪存。 |
| flash_area_close | 完成闪存操作 |
固件更新示例
设备固件可以使用特定的测试命令进行更新。例如,我们的示例更新代理使用HTTP协议进行固件下载。但值得注意的是,其他协议也可以用于此目的。在更新时,应用程序必须调用适当的闪存API函数来擦除并将更新的固件写入次要分区。
设置HTTP服务器
本节概述了在局域网(LAN)环境中建立过程。在这里,无线路由器充当网关。此外,我们将深入探讨部署HTTP服务器的步骤,该服务器将存储设备固件。如何通过Wi-Fi将TLSR9118连接到这个无线路由器的过程详细描述在"TLSR9118 WiFi软件开发指南"中。

文件传输
下图显示了镜像更新过程的开始。新镜像放置在IP地址为192.168.1.133的HTTP服务器上。
# mcuboot_agent http://192.168.1.133/wits.mcuboot.bin

成功传输完成后,设备将自动重启。随后,引导程序将交换主要和次要的固件,如下图所示。

完成交换后,引导程序将启动新的应用程序。
验证固件
要将新的固件指定为永久的,请使用以下命令:
# mcuboot_confirm

重要提示:如果跳过这个确认步骤,引导程序在下次设备重启时,将在主要和次要分区中还原固件,有效地恢复之前的固件。这是一个安全措施,假设更新的固件可能遇到执行问题。
安全启动(待定)
当启用安全启动时,设备只会启动使用用户的安全密钥签名的固件。安全启动过程分为两步:
(1) BootROM验证引导程序 (2) 引导程序反过来验证应用程序
BootROM的安全引导
如果设置了相应的eFuse位,BootROM将使能安全启动。它使用eFuse安全密钥验证引导程序的签名,或位于闪存开头的固件。此固件应具有Telink特定的头部和必要的详细信息。
引导程序的安全启动
如果引导程序是使用相关的menu选项启用构建的,则会使能安全启动。在启动过程中,使用安全密钥构建的引导程序将验证应用程序固件的签名。此应用程序应具有MCUBoot特定的头部和带有必要详细信息的TLVs。
闪存加密(待定)
当启用闪存加密时,更新过程会进行轻微的修改。考虑到原始固件数据(无加密)不应该被暴露,因此用于更新的固件在下载阶段之前也应该被加密。
构建系统支持闪存加密。启用后,它会生成加密的固件和原始固件。
烧录命令⾏⼯具
简介
命令行界面 (CLI, Command Line Interface) 用于编程设备,并且可以在不同的操作系统上执行。为了适应各种情况,它需要参数进行配置或使用配置文件。
为了启用CLI通信,设备需要设置为 "从UART启动"。该设备支持不同的启动模式。
| 启动模式 | GPIO3 | GPIO2 |
|---|---|---|
| Boot From Flash | 1 | 0 |
| Boot From UART | 0 | 1 |
| Boot From USB | 1 | 0 |
| Boot From SDIO | 0 | 0 |
如果设备支持硬件重置,CLI 工具将自动选择启动模式并重置设备。
在以下内容中,CLI通常被称为"sctool"。Sctool主要包括两个部分:
-
可选: 此部分指的是用于配置或修改整个过程行为的参数。
-
操作: 此部分包括执行部分,执行以下任务:
- 信息查询
- Flash操作
- eFuse操作
整个过程可以分为四个阶段。
CLI
执行指令: sctool
usage: sctool [-h] [-v VERBOSE] [-V] [-p PORT] [-da DA] [-b BAUDRATE]
[--before {hw_reset,no_reset}] [--manual]
[--after {hw_reset,sw_reset,no_reset}] [-c CONFIG]
{test_write,test_read,hw_reset,sw_reset,list_ports,chip_id,da_ver,upload_da,flash_read,fl
ash_write,flash_erase,flash_size,flash_chip_erase,efuse_read,efuse_write,efuse_read_bi
n,efuse_write_bin,efuse_size}
选项指南
-
-h: 打印帮助信息
-
-v: 输出信息的详细级别
- 0 – 只显示关键消息。
- 1 – 显示关键和信息消息(默认)。
- 2 – 显示所有消息。
-
-p: 与设备通讯的串口号
-
-da: DA 镜像的路径。
-
-b: 上传 DA 后通信的新波特率。
-
--before: 在前期阶段的动作。
- hw_reset (默认)
- no_reset
-
--after: 在后期阶段的动作。
- hw_reset
- sw_reset
- no_reset (默认)
-
--manual: 只执行操作。
-
-c: 配置文件路径。
为了满足基本的命令需求,命令中应同时包括端口号和DA。以下是读取"da_ver"的示例命令:
对于Windows系统 - read da_ver
sctool.exe -p COM3 -b 2000000 -da da/da.ram.bin da_ver
对于Ubuntu 18.04系统 - read da_ver
./sctool -p /dev/ttyUSB0 -b 2000000 -da da/da.ram.bin da_ver
以下示例将使用Windows作为模板。对于Ubuntu版本,请将"sctool.exe"替换为 "./sctool",并将 "COMX" 替换为 "/dev/ttyUSBX"。
配置文件
为了简化命令并减少操作所需的参数数量,使用配置文件会更加方便。配置文件应为INI格式,结构应类似于以下内容:
[Settings]
verbose = 1
port = COM3
agent = da/da.ram.bin
baudrate = 2000000
before = hw_reset
after = no_reset
在命令行中,通过传递“-c”参数来使用配置文件。
对于windows系统 – 读取 "da_ver"
sctool.exe -c config.ini da_ver
可以将配置文件用作默认值,并在需要时提供覆盖它的选项。
对于Windows系统 – 使用COM0读取 "da_ver"。
sctool.exe -c config.ini da_ver -p COM0
对于Windows系统 – 使用COM0和波特率115200读取 "da_ver"。
sctool.exe -c config.ini da_ver -p COM0 -b 115200
操作说明
此操作需要某些参数,并且可以添加额外的可选参数,如“--verify”。为提供一个简化的示例,以下模板假定您正在使用 Windows 系统。如果您使用的是 Ubuntu,请相应地将 "sctool.exe" 替换为 "./sctool"。
测试
test_write <SIZE>,写入特定大小以测试通信。
sctool.exe -c config.ini test_write 512
test_read <SIZE>,读取特定大小以测试通信。
sctool.exe -c config.ini test_read 512
通用操作
chip_id,读取设备的芯片 ID
sctool.exe -c config.ini chip_id
da_ver,读取da版本
sctool.exe -c config.ini da_ver
upload_da,上传da
sctool.exe -c config.ini upload_da
重置操作
hw_reset,执行硬件重置(此功能需要硬件支持)
sctool.exe -c config.ini hw_reset
sw_reset,执行软件重置(设备必须加载DA)
sctool.exe -c config.ini upload_da
sctool.exe -c config.ini sw_reset
其他操作
list_ports,列出用于UART的可用端口(此方法不需要config.ini)
sctool.exe list_ports
闪存操作
flash_read <ADDR> <SIZE> <FILENAME>
从 <ADDR> 读取大小为 <SIZE> 的闪存,并将其保存到 <FILENAME>。
由于闪存映射方法,闪存的起始地址为"0x80000000"。(0x80000000在闪存地址上意味着0x00000000)。
sctool.exe -c config.ini flash_read 0x80000000 0x2000 boot.bin
flash_write <ADDR> <FILENAME> [--verify]
从 <ADDR> 开始写入闪存,数据内容来自 <FILENAME>。
由于闪存映射方法,闪存的起始地址为"0x80000000"。(0x80000000在闪存地址上意味着0x00000000)。
sctool.exe -c config.ini flash_write 0x80000000 boot.bin
--verify: 写入数据后,读回并使用<FILENAME> 进行验证。
sctool.exe -c config.ini flash_write 0x80000000 boot.bin --verify
注意
- flash_write将自动执行擦除操作,擦除块为4 KB对齐。
flash_erase <ADDR> <SIZE>
从 <ADDR> 开始擦除大小为 <SIZE> 的闪存。
sctool.exe -c config.ini flash_erase 0x80000000 0x2000
注意
- 请注意,擦除操作要求大小(<SIZE>)与 4KB 对齐。这意味着实际大小应增加0x1000。
flash_size,获取闪存大小 (Bytes)。
sctool.exe -c config.ini flash_size
flash_chip_erase,执行芯片擦除。
sctool.exe -c config.ini flash_chip_erase
eFuse操作
efuse_read <START_BIT> <BIT_WIDTH>
从 <START_BIT> 读取 eFuse 数据,位宽为 <BIT_WIDTH>。
sctool.exe -c config.ini efuse_read 0 8
efuse_write <START_BIT> <BIT_WIDTH> <HEX_DATA>
从 <START_BIT> 写入eFuse数据,位宽为 <BIT_WIDTH>,数据为 <HEX_DATA>。
sctool.exe -c config.ini efuse_write 0 64 0x0123456789ABCDEF
注意
- 使用efuse_write功能将永久更改efuse的行为。efuse_write只能将数据从“0”更改为“1”。
efuse_read_bin
读取整个eFuse并将其保存到<FILENAME>。
sctool.exe -c config.ini efuse_read_bin efuse.bin
efuse_write_bin <FILENAME> [--verify]
使用来自<FILENAME> 的内容编写整个eFuse数据。
sctool.exe -c config.ini efuse_write_bin efuse.bin
--verify: 写入eFuse数据后,读回并使用<FILENAME>进行验证。
sctool.exe -c config.ini efuse_write_bin efuse.bin --verify
注意
- efuse_write_bin只能将数据从“0”更改为“1”。
efuse_size,获取eFuse大小(位)
sctool.exe -c config.ini efuse_size
示例
Flash操作
XIP启动
sctool.exe -c config.ini --after hw_reset flash_write 0x80000000 image/nuttx.tlsrboot.xip.bin
Nuttx启动
sctool.exe -c config.ini --after hw_reset flash_write 0x80040000 image/nuttx.mcuboot.xip.bin
Wits
sctool.exe -c config.ini --after hw_reset flash_write 0x80140000 image/wits.bin
手动烧录多扇区
sctool.exe -c config.ini --before=hw_reset --after=no_reset upload_da
sctool.exe -c config.ini --manual flash_write 0x80000000 image/nuttx.tlsrboot.xip.bin
sctool.exe -c config.ini --manual flash_write 0x80040000 image/nuttx.mcuboot.xip.bin
sctool.exe -c config.ini --manual flash_write 0x80140000 image/wits.bin
sctool.exe -c config.ini hw_reset
对于不支持安全恢复机制的设备
如果您的设备不支持安全恢复机制,您需要逐步使设备进入“通过UART启动”并进行编程。请按照以下说明操作。
如果您的设备不支持安全恢复机制,那么您需要经过一系列的步骤来进入“通过UART启动”模式并进行编程。在这一章中,您将找到关于如何为这类设备进行故障排除,并成功初始化UART通讯的启动过程的详细说明。请按照以下步骤进行,以确保编程过程的顺利和高效。
-
步骤 1: 将SW2切换至“0100”,以实现“通过UART启动”。
-
步骤 2: 打开目标设备电源。
-
步骤 3: 执行CLI操作,它将等待设备进入UART模式。例如: sctool.exe -c config.ini flash_read 0x80000000 0x2000 boot.bin
-
步骤 4: 触发SW3按钮进行硬件重置以进入UART模式并等待命令完成。
-
步骤 5: 将SW2恢复为“从Flash启动”,然后触发SW3进行重启。
常见问题
Q: 出现错误信息: “Error: Could not open serial port COMn” on Windows or “Error: Could not open serial port /dev/usbttyn” on Linux
A: 看起来操作系统没有检测到COM端口,您可以输入以下命令来检查可用的COM端口并选择其中一个。
sctool.exe list_ports
Available Ports:
COM1
COM4
Q: 出现错误信息: “Error: Serial Port Read Timeout!”
A: COM 端口已经被检测到,但通信失败。请检查您是否已经将 EVB/EVK 切换至“通过UART 启动”模式。在设置“通过 UART 启动”模式并触发重置按钮后,您将在终端上看到"0x00000000 CCC..."。
Q: 为什么在输入'sctool.exe xxx' 命令后需要立即点击重置按钮?
A: Flash 工具需要目标设备上的兼容 DA 镜像来处理相关命令。“通过 UART 启动”模式可以允许 sctool.exe 上传 DA。如果设备不在“通过 UART 启动”模式,命令将每秒检查一次,直到等待10秒。这就是为什么我们需要在10秒内点击重置按钮,进入“通过 UART 启动”模式,让sctool.exe 完成其命令的原因。
Q: 在Linux电脑上,如何确认用于刷写的镜像端口,是 /dev/ttyUSB0 还是 /dev/ttyUSB1?
A: 在Linux上,它会将第一个USB-UART设置为 /dev/ttyUSB0,第二个设置为/dev/ttyUSB1。
为了确定哪个端口正在用于刷写图像,您可以使用minicom来检查其中哪个在“通过UART启动”模式下持续输出 "CCC…"。以下是示例。

单板冒烟测试指南
概述
冒烟测试
冒烟测试是软件开发过程中的初步测试,主要对软件版本包进行快速的基本功能验证,而不是深入的测试。在进行详细测试之前,首先要执行冒烟测试,其主要目的是快速检查软件的基本功能是否存在缺陷。冒烟测试主要包括Ping测试和Iperf测试。
Ping测试
Ping是一个网络测试工具,主要用于检测网络连接的质量。它通过发送ICMP回应请求消息到指定的网络主机,并等待其回应,从而判断网络连接的稳定性。
Iperf测试
IPerf是一个网络性能测试工具,可以测量TCP和UDP的最大带宽性能。它能够测试从一端到另一端的TCP或UDP流量,并提供网络带宽、延迟、抖动和数据包损失的相关信息。
冒烟测试软件编译
软件编译流程
要执行单板冒烟测试,首先需要编译相应的软件。请按照以下步骤操作:
步骤1:选择对应的config,即tlsr9118s_defconfig
$ cd wits-sdk
$ make distclean
$ make tlsr9118s_defconfig
步骤2:启用测试功能
$ make menuconfig
(1) 进入SDK

(2) 勾选Include Wi-FI CLIs for API testing,并且进入Wi-Fi CLI

(3) 选择开启必要的功能: STA、SCAN、SAP

步骤3:编译
$ make
步骤4:确认在wits-sdk路径下生成了wits.mcuboot.bin文件。
Wi-Fi STA单板冒烟测试
概述
任何一个接入无线AP的设备都可以称为一个站点(STA: Station)。在STA模式的冒烟测试中,主要是实现与AP设备的连接并进行数据通信。
测试流程
通过Wi-Fi help指令,可以显示如下的指令列表:

步骤1:复位单板

步骤2:启动 STA

步骤3:扫描网络

步骤4:查看扫描结果

步骤5:配置连线网络资讯

步骤6:连接网络

步骤7:启动DHCP Client

步骤8:查看STA网络信息

步骤9:ping测试

步骤10:Iperf测试

Wi-Fi AP单板冒烟测试
概述
无线接入点(Wireless Access Point,简称AP)是无线网络中的一个关键设备。AP作为一个网络接口,可以让无线设备连接。它还可以作为无线路由器、无线网关、无线网桥等设备的统称。在本次AP模式的冒烟测试中,主要是实现单板作为一个AP设备与STA设备进行通信。
测试流程
通过Wi-Fi help指令,可以显示如下的指令列表:

步骤 1: 复位单板

步骤 2: 设置AP

步骤 3: 启动 AP

步骤 4: 设置IP地址

步骤 5: 启动DHCP server

步骤 6: 查看AP配置信息

步骤 7: STA连接AP

步骤 8: 查看STA信息

步骤 9: AP ping STA

步骤 10: Iperf测试

CMSIS-FreeRTOS API指南
简介
TLSR9118 SDK集成了CMSIS-FreeRTOS作为一个API,使用户能够在他们的应用程序中使用多种操作系统功能。虽然原生FreeRTOS的数据类型和函数仍然可以使用,但强烈推荐在应用层使用CMSIS-FreeRTOS API。
概述
TLSR9118 SDK使用了CMSIS-FreeRTOS接口:
-
头文件
- include/cmsis_os.h
-
定义
- include/hal/cmsis_os2.h
应用程序应当引用<include/cmsis_os.h>而非<include/hal/cmsis_os2.h>。
构建
无需特别设置即可启用CMSIS-FreeRTOS API,因为它默认总是开启的。
参考
TLSR9118 SDK中的CMSIS-FreeRTOS基于CMSIS-RTOS API v2 API,分为以下几个类别:
-
内核信息和控制
-
线程管理
-
线程标志
-
事件标志
-
通用等待函数
-
定时器管理
-
互斥锁管理
-
信号量
-
内存池 (当前不支持)
-
消息队列
这些类别将在下文中详细介绍。
内核信息和控制
内核信息和控制函数组允许进行以下操作:
-
获取有关系统和底层内核的信息。
-
获取 CMSIS-RTOS API 的版本信息。
-
初始化 RTOS 内核以创建对象。
-
启动 RTOS 内核和线程切换。
-
检查 RTOS 内核的执行状态。
内核信息和控制函数不能从中断服务例程 (ISR) 中调用。
数据结构
- struct osVersion_t
标识底层RTOS内核和API版本号。
版本以组合十进制数的格式表示:主版本号.次版本号.修订号: mmnnnrrrr
| 数据字段 | - | 版本 |
|---|---|---|
| uint32_t | api | API版本(主版本号.次版本号.修订号: mmnnnrrrr十进制)。 |
| uint32_t | kernel | 内核版本(主版本号.次版本号.修订号: mmnnnrrrr十进制)。 |
使用osKernelGetInfo来检索版本号。
枚举类型
- Enum osKernelState_t
通过osKernelGetState检索的内核状态。
如果osKernelGetState失败或者在ISR中调用,它将返回osKernelError,否则返回内核状态。
| 枚举器 | 状态 |
|---|---|
| osKernelInactive | 非活动。内核尚未准备好。需要成功执行osKernelInitialize。 |
| osKernelReady | 准备就绪。内核尚未运行。osKernelStart将内核转移到运行状态。 |
| osKernelRunning | 运行中。内核已初始化并运行。 |
| osKernelLocked | 锁定。内核已使用osKernelLock锁定。函数osKernelUnlock或osKernelRestoreLock解锁它。 |
| osKernelSuspended | 挂起。内核使用osKernelSuspend挂起。函数osKernelResume返回正常操作。 |
| osKernelError | 错误。发生错误。 |
| osKernelReserved | 防止枚举下调编译器优化。保留。 |
函数
- osStatus_t osKernelInitialize(void)
函数osKernelInitialize初始化RTOS内核。
在成功执行之前,只能调用函数osKernelGetInfo和osKernelGetState。
| 返回值 | 描述 |
|---|---|
| osOK | 成功时。 |
| osError | 发生未指定的错误时。 |
| osErrorISR | 如果从ISR中调用。 |
| osErrorNoMemory | 如果无法为操作保留内存。 |
- osStatus_t osKernelGetInfo(osVersion_t version, char id_buf, uint32_t *id_size)
函数osKernelGetInfo检索底层RTOS内核的API和内核版本以及内核的人类可读标识符字符串。在初始化或启动RTOS(调用osKernelInitialize或osKernelStart)之前可以安全地调用它。
| 参数 | 类型 | 描述 |
|---|---|---|
| version | 输出参数 | 指向用于检索版本信息的缓冲区的指针。 |
| id_buf | 输出参数 | 指向用于检索内核标识字符串的缓冲区的指针。 |
| id_size | 输入参数 | 内核标识字符串的缓冲区大小。 |
| 返回值 | 描述 |
|---|---|
| osOK | 成功时。 |
| osError | 发生未指定的错误时。 |
| osErrorISR | 如果从ISR中调用。 |
- osKernelState_t osKernelGetState(void)
函数osKernelGetState返回内核的当前状态,并且在初始化或启动RTOS(调用osKernelInitialize或osKernelStart)之前可以安全地调用。如果失败,它将返回osKernelError,否则返回内核状态(参见osKernelState_t以获取内核状态列表)。
| 返回值 | 描述 |
|---|---|
| osKernelError | 如果从ISR中调用。 |
| 实际内核状态 | 其它场景。 |
- osStatus_t osKernelStart(void)
函数osKernelStart启动RTOS内核并开始线程切换。在成功的情况下,它不会返回到其调用函数。在成功执行之前,只能调用函数osKernelGetInfo、osKernelGetState和对象创建函数(osXxxNew)。
在osKernelStart之前,至少应创建一个初始线程,请参见osThreadNew。
| 返回值 | 描述 |
|---|---|
| osOK | 成功时。 |
| osError | 发生未指定的错误时。 |
| osErrorISR | 如果从ISR中调用。 |
- int32_t osKernelLock(void)
函数osKernelLock允许锁定所有任务切换。它返回锁定状态的前一个值(如果锁定,则为1;如果未锁定,则为 0),或者否则返回表示错误代码的负数(参见 osStatus_t)。
| 返回值 | 描述 |
|---|---|
| osError | 发生未指定的错误时。 |
| osErrorISR | 如果从ISR中调用。 |
| 锁定状态的前一个值 | 其它场景。 |
- int32_t osKernelUnlock(void)
函数osKernelUnlock从osKernelLock恢复。它返回锁定状态的前一个值(如果锁定,则为1;如果未锁定,则为0),或者否则返回表示错误代码的负数(参见osStatus_t)。
| 返回值 | 描述 |
|---|---|
| osError | 发生未指定的错误时。 |
| osErrorISR | 如果从ISR中调用。 |
| 锁定状态的前一个值 | 其它场景。 |
- int32_t osKernelRestoreLock(int32_t lock)
函数osKernelRestoreLock在osKernelLock或osKernelUnlock之后恢复前一个锁定状态。参数lock指定了由osKernelLock或osKernelUnlock获得的锁定状态。
函数返回锁定状态的新值(如果锁定,则为1;如果未锁定,则为 0),或者否则返回表示错误代码的负数(参见osStatus_t)。
| 返回值 | 描述 |
|---|---|
| osError | 发生未指定的错误时。 |
| osErrorISR | 如果从ISR中调用。 |
| 新的锁定状态 | 其它场景。 |
- uint32_t osKernelSuspend(void)
不支持。
- uint32_t osKernelResume(void)
不支持。
- uint32_t osKernelGetTickCount(void)
函数osKernelGetTickCount返回当前RTOS内核的tick计数。
- uint32_t osKernelGetTickFreq(void)
函数osKernelGetTickFreq返回当前RTOS内核tick的频率。
- uint32_t osKernelGetSysTimerCount(void)
函数osKernelGetSysTimerCount返回当前RTOS内核系统计时器的值,为32位值。该值是由内核系统中断计时器值和计数这些中断的计数器(RTOS内核tick)组成的滚动32位计数器。
该函数允许实现低于RTOS tick粒度的非常短的超时检查。在设备或外设初始化例程中检查忙状态时,可能需要进行此类检查。
- uint32_t osKernelGetSysTimerFreq(void)
函数osKernelGetSysTimerFreq返回当前RTOS内核系统计时器的频率。
线程管理
线程管理功能组允许在系统中定义、创建和控制线程功能。
线程可以处于以下状态:
-
运行中 (RUNNING):当前运行的线程处于RUNNING状态。一次只能有一个线程处于此状态。
-
就绪 (READY):准备运行的线程处于READY状态。一旦运行中的线程终止或被阻塞,具有最高优先级的下一个就绪线程成为运行中的线程。
-
阻塞 (BLOCKED):被延迟、等待事件发生或被挂起的线程处于BLOCKED状态。
-
终止 (TERMINATED):调用osThreadTerminate时,线程被终止,资源尚未释放(适用于可连接的线程)。
-
非活动 (INACTIVE):未创建或已终止且所有资源已释放的线程处于INACTIVE状态。
线程状态的变化如下:
-
使用函数osThreadNew创建线程。这会将线程置于就绪或运行中状态(取决于线程优先级)。
-
CMSIS-RTOS是抢占式的。具有最高优先级的活动线程成为运行中的线程,前提是它不等待任何事件。线程的初始优先级由osThreadAttr_t定义,但可以在执行过程中使用osThreadSetPriority函数更改。
-
运行中的线程在延迟、等待事件或被挂起时转入阻塞状态。
-
活动线程可以随时使用osThreadTerminate函数终止。线程也可以通过从线程函数返回来终止。被终止的线程处于非活动状态,通常不消耗任何动态内存资源。

数据结构
- struct osThreadAttr_t
| 数据字段 | - | 描述 |
|---|---|---|
| const char * | name | 线程的名称。指向线程对象的可读名称(在调试期间显示)的常量字符串的指针。默认值:NULL,没有指定名称(调试器可能显示函数名称)。 |
| uint32_t | attr_bits | 属性位。可以使用以下位掩码来设置选项:osThreadDetached: 以分离模式创建线程(默认);osThreadJoinable: 以可连接模式创建线程。 |
| void * | cb_mem | 控制块的内存。指向线程控制块对象的内存的指针。默认值:NULL,使用自动动态分配线程控制块的内存。 |
| uint32_t | cb_size | 提供的控制块内存的大小用cb_mem传递的内存块的大小(以字节为单位)。默认值:0表示没有使用cb_mem提供的内存。 |
| void * | stack_mem | 栈的内存。指向线程栈的内存位置的指针(64 位对齐)。默认值:NULL,使用线程栈管理从固定大小的内存池分配栈。 |
| uint32_t | stack_size | 栈的大小。指定的栈的大小(以字节为单位)。默认值:0表示没有使用stack_mem提供的内存。 |
| osPriority_t | priority | 初始线程优先级(默认:osPriorityNormal)。使用osPriority_t的值指定初始线程优先级。默认值:osPriorityNormal。 |
| TZ_ModuleId_t | tz_module | 模块标识符。TrustZone线程上下文管理标识符,用于为线程分配上下文内存。在非安全状态下运行的RTOS内核调用由头文件TZ_context.h定义的接口函数。对于根本不使用安全调用的线程,可以安全地设置为零。参见TrustZone RTOS上下文管理。默认值:0没有指定线程上下文。 |
| uint32_t | reserved | 保留(必须为 0)。保留以备将来使用。 |
类型定义
- void (osThreadFunc_t)(void argument)
线程的入口函数。设置新线程(osThreadNew)将通过调用这个入口函数开始执行。可选的参数可用于将任意用户数据传递给线程,即用于识别线程或用于运行时参数。
| 参数 | 类型 | 描述 |
|---|---|---|
| argument | 输入参数 | 在osThreadNew上设置的任意用户数据。 |
- osThreadId_t
线程ID标识线程。
由以下返回:
-
osThreadNew
-
osThreadGetId
-
osThreadEnumerate
-
osMutexGetOwner
枚举类型
- enum osThreadState_t
通过osThreadGetState检索的线程状态。如果osThreadGetState失败或从ISR调用,它将返回osThreadError,否则返回线程状态。
| 枚举器 | 描述 |
|---|---|
| osThreadInactive | 非活动。线程已创建但未被积极使用,或已被终止(用于静态控制块分配,当使用内存池时返回osThreadError,因为控制块不再有效) |
| osThreadReady | 就绪。线程已准备好执行,但当前未运行。 |
| osThreadRunning | 运行中。线程当前正在运行。 |
| osThreadBlocked | 阻塞。线程当前被阻塞(延迟、等待事件或被挂起)。 |
| osThreadTerminated | 终止。线程被终止,所有资源尚未释放(适用于可连接的线程)。 |
| osThreadError | 错误。线程不存在(引发错误条件)且不能被调度。 |
| osThreadReserved | 防止枚举下调编译器优化。 |
- enum osPriority_t
osPriority_t值指定线程的优先级。默认线程优先级应为osPriorityNormal。如果一个活动线程变为就绪,并且其优先级高于当前运行的线程,那么将立即发生线程切换。系统继续执行具有较高优先级的线程。
| 枚举器 | 描述 |
|---|---|
| osPriorityNone | 无优先级(未初始化)。 |
| osPriorityIdle | 保留给空闲线程。这个最低优先级不应该用于任何其他线程。 |
| osPriorityLow | 优先级:低。 |
| osPriorityLow1 | 优先级:低 + 1。 |
| osPriorityLow2 | 优先级:低 + 2。 |
| osPriorityLow3 | 优先级:低 + 3。 |
| osPriorityLow4 | 优先级:低 + 4。 |
| osPriorityLow5 | 优先级:低 + 5。 |
| osPriorityLow6 | 优先级:低 + 6。 |
| osPriorityLow7 | 优先级:低 + 7。 |
| osPriorityNormal5 | 优先级:正常 + 5。 |
| osPriorityNormal6 | 优先级:正常 + 6。 |
| osPriorityNormal7 | 优先级:正常 + 7。 |
| osPriorityAboveNormal | 优先级:高于正常。 |
| osPriorityAboveNormal1 | 优先级:高于正常 + 1。 |
| osPriorityAboveNormal2 | 优先级:高于正常 + 2。 |
| osPriorityAboveNormal3 | 优先级:高于正常 + 3。 |
| osPriorityAboveNormal4 | 优先级:高于正常 + 4。 |
| osPriorityAboveNormal5 | 优先级:高于正常 + 5。 |
| osPriorityAboveNormal6 | 优先级:高于正常 + 6。 |
| osPriorityAboveNormal7 | 优先级:高于正常 + 7。 |
| osPriorityHigh | 优先级:高。 |
| osPriorityHigh1 | 优先级:高 + 1。 |
| osPriorityHigh2 | 优先级:高 + 2。 |
| osPriorityHigh3 | 优先级:高 + 3。 |
| osPriorityHigh4 | 优先级:高 + 4。 |
| osPriorityHigh5 | 优先级:高 + 5。 |
| osPriorityHigh6 | 优先级:高 + 6。 |
| osPriorityHigh7 | 优先级:高 + 7。 |
| osPriorityRealtime | 优先级:实时。 |
| osPriorityRealtime1 | 优先级:实时 + 1。 |
| osPriorityRealtime2 | 优先级:实时 + 2。 |
| osPriorityRealtime3 | 优先级:实时 + 3。 |
| osPriorityRealtime4 | 优先级:实时 + 4。 |
| osPriorityRealtime5 | 优先级:实时 + 5。 |
| osPriorityRealtime6 | 优先级:实时 + 6。 |
| osPriorityRealtime7 | 优先级:实时 + 7。 |
| osPriorityISR | 保留给ISR延迟线程。这个最高优先级可能被RTOS实现使用,但不得用于任何用户线程。 |
| osPriorityError | 系统无法确定优先级或优先级非法。 |
| osPriorityReserved | 防止枚举下调编译器优化。 |
函数
- osThreadId_t osThreadNew(osThreadFunc_t func, void argument,const osThreadAttr_t attr)
函数osThreadNew通过将线程函数添加到活动线程列表并将其设置为READY状态来启动线程函数。使用参数指针*argument传递线程函数的参数。当创建的线程函数的优先级高于当前RUNNING线程时,创建的线程函数立即启动并成为新的RUNNING线程。使用参数指针attr定义线程属性。属性包括线程优先级、栈大小或内存分配的设置。
在RTOS启动之前(调用 osKernelStart)可以安全地调用该函数,但在初始化之前(调用 osKernelInitialize)不能调用。
函数osThreadNew返回指向线程对象标识符的指针,如果出错,则返回NULL。
| 参数 | 类型 | 描述 |
|---|---|---|
| func | 输入参数 | 线程函数。 |
| argument | 输入参数 | 传递给线程函数作为开始参数的指针。 |
| attr | 输入参数 | 线程属性;NULL:默认值。 |
| 返回值 | 描述 |
|---|---|
| thread ID | 成功时。 |
| NULL | 出错时。 |
- const char *osThreadGetName(osThreadId_t thread_id)
函数osThreadGetName返回由参数thread_id标识的线程的名称字符串的指针,如果出错,则返回NULL。
| 参数 | 类型 | 描述 |
|---|---|---|
| thread_id | 输入参数 | 由osThreadNew或osThreadGetId获得的线程ID。 |
| 返回值 | 描述 |
|---|---|
| 以null结尾的线程名称字符串 | 成功时。 |
| NULL | 出错时。 |
- osThreadId_t osThreadGetId(void)
函数osThreadGetId返回当前运行线程的线程对象ID,如果出错,则返回NULL。
| 返回值 | 描述 |
|---|---|
| thread ID | 成功时。 |
| NULL | 出错时。 |
- osThreadState_t osThreadGetState(osThreadId_t thread_id)
函数osThreadGetState返回由参数thread_id标识的线程的状态。如果失败或从ISR中调用,它将返回osThreadError,否则返回线程状态(参见osThreadState_t以获取线程状态列表)。
| 参数 | 类型 | 描述 |
|---|---|---|
| thread_id | 输入参数 | 由osThreadNew或osThreadGetId获得的线程ID。 |
| 返回值 | 描述 |
|---|---|
| 当前线程状态 | 成功时。 |
| osTheadError | 出错时。 |
- osStatus_t osThreadSetPriority(osThreadId_t thread_id, osPriority_t priority)
函数osThreadSetPriority更改由参数thread_id指定的活动线程的优先级,将其更改为参数priority指定的优先级。
| 参数 | 类型 | 描述 |
|---|---|---|
| thread_id | 输入参数 | 由osThreadNew或osThreadGetId获得的线程ID。 |
| priority | 输入参数 | 线程函数的新优先级值。 |
| 返回值 | 描述 |
|---|---|
| osOK | 指定线程的优先级已成功更改。 |
| osErrorParameter | 为NULL或无效或优先级不正确。 |
| osErrorResource | 线程处于无效状态。 |
| osErrorISR | osThreadSetPriority不能从中断服务例程中调用。 |
- osPriority_t osThreadGetPriority(osThreadId_t thread_id)
函数osThreadGetPriority返回由参数thread_id指定的活动线程的优先级。
| 参数 | 类型 | 描述 |
|---|---|---|
| thread_id | 输入参数 | 由osThreadNew或osThreadGetId获得的线程ID。 |
| 返回值 | 描述 |
|---|---|
| priority | 指定线程的优先级。 |
| osPriorityError | 无法确定优先级或优先级非法。当函数从中断服务例程中调用时也返回此值。 |
- osStatus_t osThreadYield(void)
函数osThreadYield将控制权传递给处于READY状态的具有相同优先级的下一个线程。如果没有处于READY状态的具有相同优先级的其他线程,则当前线程继续执行,不会发生线程切换。osThreadYield不会将线程设置为BLOCKED状态。因此,即使有处于READY状态的线程,也不会调度具有较低优先级的线程。
当内核被锁定时调用此函数时,不会产生影响,参见osKernelLock。
| 返回值 | 描述 |
|---|---|
| osOK | 控制权已成功传递给下一个线程。 |
| osError | 发生了未指定的错误。 |
| osErrorISR | 函数 osThreadYield 不能从中断服务例程中调用。 |
- osStatus_t osThreadSuspend(osThreadId_t thread_id)
函数osThreadSuspend暂停由参数thread_id标识的线程的执行。线程被置于BLOCKED状态 (osThreadBlocked)。暂停运行的线程将立即导致切换到另一个处于READY状态的线程。被暂停的线程在使用函数osThreadResume明确恢复之前不会执行。
已经处于BLOCKED状态的线程将从任何等待列表中移除,并在恢复时变为就绪。因此,不建议暂停已经被阻塞的线程。
当内核被锁定时,不得调用此函数来暂停运行的线程,即osKernelLock。
| 参数 | 类型 | 描述 |
|---|---|---|
| thread_id | 输入参数 | 由osThreadNew或osThreadGetId获得的线程ID。 |
| 返回值 | 描述 |
|---|---|
| osOK | 线程已成功暂停。 |
| osErrorParameter | thread_id为NULL或无效。 |
| osErrorResource | 线程处于无效状态。 |
| osErrorISR | 函数osThreadSuspend不能从中断服务例程中调用。 |
- osStatus_t osThreadResume(osThreadId_t thread_id)
函数osThreadResume将由参数thread_id标识的线程(必须处于BLOCKED 状态)恢复到READY状态。如果恢复的线程的优先级高于运行中的线程,则立即发生上下文切换。
线程变为就绪,无论阻塞线程的原因如何。因此,不建议恢复未被osThreadSuspend暂停的线程。
会将线程置于BLOCKED状态的函数包括:osEventFlagsWait和osThreadFlagsWait, osDelay和osDelayUntil, osMutexAcquire和osSemaphoreAcquire, osMessageQueueGet, osMemoryPoolAlloc, osThreadJoin, osThreadSuspend。
当内核被锁定(osKernelLock)时,可以调用此函数。在这种情况下,潜在的上下文切换将被延迟,直到内核解锁,即osKernelUnlock或osKernelRestoreLock。
| 参数 | 类型 | 描述 |
|---|---|---|
| thread_id | 输入参数 | 由osThreadNew或osThreadGetId获得的线程ID。 |
| 返回值 | 描述 |
|---|---|
| osOK | 线程已成功暂停。 |
| osErrorParameter | thread_id为NULL或无效。 |
| osErrorResource | 线程处于无效状态。 |
| osErrorISR | 函数osThreadSuspend不能从中断服务例程中调用。 |
- osStatus_t osThreadDetach(osThreadId_t thread_id)
不支持。
- osStatus_t osThreadJoin(osThreadId_t thread_id)
不支持。
- __NO_RETURN void osThreadExit(void)
函数osThreadExit终止调用线程。这允许线程与osThreadJoin同步。
- osStatus_t osThreadTerminate(osThreadId_t thread_id)
函数osThreadTerminate从活动线程列表中移除由参数thread_id指定的线程。如果线程当前正在运行,线程将终止,并且执行将继续下一个READY线程。如果不存在这样的线程,函数将不会终止运行的线程,而是返回osErrorResource。
避免使用不存在或已被终止的线程ID调用该函数。
osThreadTerminate销毁非可连接线程并从系统中移除它们的thread_id。
后续访问thread_id(例如 osThreadGetState)将返回osThreadError。可连接线程将不会被销毁,并在与 osThreadJoin 结合使用后返回状态 osThreadTerminated。
| 参数 | 类型 | 描述 |
|---|---|---|
| thread_id | 输入参数 | 由osThreadNew或osThreadGetId获得的线程ID。 |
| 返回值 | 描述 |
|---|---|
| osOK | 指定线程已成功从活动线程列表中移除。 |
| osErrorParameter | thread_id为NULL或无效。 |
| osErrorResource | 线程处于无效状态或不存在其他READY线程。 |
| osErrorISR | 函数osThreadTerminate不能从中断服务例程中调用。 |
- uint32_t osThreadGetStackSize(osThreadId_t thread_id)
不支持。
- uint32_t osThreadGetStackSpace(osThreadId_t thread_id)
函数osThreadGetStackSpace返回由参数thread_id指定的线程的未使用栈空间的大小。需要启用执行期间的栈水印记录(参见线程配置)。如果出错,返回0。
| 参数 | 类型 | 描述 |
|---|---|---|
| thread_id | 输入参数 | 由osThreadNew或osThreadGetId获得的线程ID。 |
| 返回值 | 描述 |
|---|---|
| 剩余栈大小(以字。节为单位) | 成功时 |
| 0 | 出错时。 |
- uint32_t osThreadGetCount(void)
函数osThreadGetCount返回活动线程的数量,如果出错,则返回0。
| 返回值 | 描述 |
|---|---|
| 活动线程的数量 | 成功时。 |
| 0 | 出错时。 |
- uint32_t osThreadEnumerate(osThreadId_t thread_array, uint32_t array_items)
函数osThreadEnumerate返回枚举的线程数量,如果出错,则返回0。
| 参数 | 类型 | 描述 |
|---|---|---|
| thread_array | 输出参数 | 指向用于检索线程 ID 的数组的指针。 |
| array_items | 输入参数 | 用于检索线程 ID 的数组中的最大项目数。 |
| 返回值 | 描述 |
|---|---|
| 枚举的线程数量 | 成功时。 |
| 0 | 出错时。 |
线程标志
线程标志是事件标志的更专业版本。参见事件标志。虽然事件标志可以用来全局信号通知多个线程,但线程标志只发送给一个特定的线程。每个线程实例都可以接收线程标志,无需额外分配线程标志对象。线程标志管理函数不能从中断服务例程中调用,osThreadFlagsSet除外。
使用示例如下:

函数
- uint32_t osThreadFlagsSet(osThreadId_t thread_id, uint32_t flags)
osThreadFlagsSet函数用于设置参数thread_id指定的线程的线程标志。线程将返回存储在线程控制块中的标志,如果最高位被设置,则返回错误代码(请参阅标志函数错误代码)。请查阅下面的使用示例,以了解返回值的计算方式。
等待设置标志的目标线程将从BLOCKED状态恢复。
| 参数 | 类型 | 描述 |
|---|---|---|
| thread_id | 输入参数 | 由osThreadNew或osThreadGetId获得的线程ID。 |
| flags | 输入参数 | 指定应设置的线程标志。 |
| 返回值 | 描述 |
|---|---|
| 设置后的线程标志 | 成功后。 |
| osFlagsErrorUnknown | 未指定的错误。 |
| osFlagsErrorParameter | 参数thread_id不是有效的线程,或者flags的最高位已设置。 |
| osFlagsErrorResource | 线程处于无效状态。 |
- uint32_t osThreadFlagsClear(uint32_t flags)
函数osThreadFlagsClear用于清除当前运行线程的指定标志。它返回清除前的标志,或者如果最高位被设置,则返回错误代码(请参考标志函数错误代码)。
| 参数 | 类型 | 描述 |
|---|---|---|
| flags | 输入参数 | 指定应设置的线程标志。 |
| 返回值 | 描述 |
|---|---|
| 返回清除前的线程标志 | 成功后。 |
| osFlagsErrorUnknown | 未指定的错误,即不是从运行线程的上下文中调用。 |
| osFlagsErrorParameter | 参数flags的最高位已设置。 |
| osFlagsErrorISR | 函数osThreadFlagsClear不能从中断服务程序中调用。 |
- uint32_t osThreadFlagsGet(void)
函数osThreadFlagsGet返回当前运行线程当前设置的标志。如果在没有活动且当前运行线程的情况下调用osThreadFlagsGet,则返回零。
返回值:当前线程的标志
- uint32_t osThreadFlagsWait(uint32_t flags, uint32_t options, uint32_t timeout)
函数osThreadFlagsWait会暂停当前正在运行的线程,直到参数flags中指定的任一或全部线程标志被设置。如果这些线程标志已经被设置,该函数将立即返回;否则,线程将进入阻塞状态。
参数options用于指定等待条件:
| 选项 | 描述 |
|---|---|
| osFlagsWaitAny | 等待任意一个标志(默认)。 |
| osFlagsWaitAll | 等待所有标志。 |
| osFlagsNoClear | 不清除已指定等待的标志。 |
如果options中设置了osFlagsNoClear,可以使用osThreadFlagsClear函数手动清除标志。否则,osThreadFlagsWait函数将自动清除已等待的标志。
参数timeout表示计时器滴答数,是一个上限值。实际的延迟时间取决于自上次计时器滴答以来经过的时间。
| 参数 | 类型 | 描述 |
|---|---|---|
| flags | 输入参数 | 指定要等待的标志。 |
| options | 输入参数 | 指定标志选项(osFlagsXxxx)。 |
| timeout | 输入参数 | 超时值;如果没有超时则为0。 |
| 返回值 | 描述 |
|---|---|
| 清除线程标志之前的标志。 | 成功后。 |
| osFlagsErrorUnknown | 未指定的错误,即不是从运行线程的上下文中调用。 |
| osFlagsErrorParameter | 参数flags的最高位已设置。 |
| osFlagsErrorTimeout | 在给定时间内未设置待定的标志。 |
| osFlagsErrorResource | 在未指定超时的情况下未设置待定的标志。 |
事件标志
CMSIS-RTOS中的事件标志管理功能允许您对事件标志进行控制或等待操作。每个线程最多可以设置 31 个事件标志。
一个线程具有以下操作事件标志的能力:
- 等待特定的事件标志被设置 (使用osEventFlagsWait函数),在此期间线程进入阻塞 (BLOCKED)状态。
- 在任意其他线程中设置一个或多个事件标志 (使用osEventFlagsSet函数)。
- 清除自身或其他线程的事件标志(使用 osEventFlagsClear 函数)。
当一个阻塞的线程被唤醒恢复执行时,其拥有的事件标志将自动清除(除非指定了osFlagsNoClear事件标志选项来保留标志状态)。
osEventFlagsSet、osEventFlagsClear、osEventFlagsGet 和osEventFlagsWait等函数可在中断服务例程中调用。
基于事件标志的线程同步操作:

数据结构
- struct osEventFlagsAttr_t
| 数据字段 | - | 描述 |
|---|---|---|
| const char * | name | 事件标志对象的名称字符串指针。指向一个常量字符串,用于在调试时显示事件标志对象的可读名称。默认值:NULL,表示未指定对象名称。 |
| uint32_t | attr_bits | 属性位。保留供将来使用,必须设置为0以保证向后兼容。 |
| void * | cb_mem | 事件标志控制块内存指针。指向用于存放事件标志控制块对象的内存区域。默认值:NULL,表示使用内核自动动态分配内存。 |
| uint32_t | cb_size | 控制块内存区域大小(字节)。通过 cb_mem 参数传入的内存区域大小(以字节为单位)。默认值:0,表示未提供cb_mem 内存区域。 |
类型定义
- osEventFlagsId_t
事件标志ID类型,用于标识一个事件标志对象。
函数
- osEventFlagsId_t osEventFlagsNew(const osEventFlagsAttr_t *attr)
该函数用于创建一个新的事件标志对象,该对象用于在线程间传递事件,并返回指向该事件标志对象标识符的指针,或在发生错误时返回NULL。在RTOS启动之前(调用 osKernelStart)可以安全地调用此函数,但不能在初始化之前(调用 osKernelInitialize)调用。
参数 attr 用于设置事件标志的属性(参见 osEventFlagsAttr_t)。如果设置为 NULL,则使用默认属性,即使用内核内存分配用于事件控制块。
| 参数 | 类型 | 描述 |
|---|---|---|
| attr | 输入 | 事件标志属性;NULL 表示使用默认值。 |
| 返回值 | 描述 |
|---|---|
| 事件标志ID。 | 成功后。 |
| NULL | 出错时。 |
- uint32_t osEventFlagsSet(osEventFlagsId_t ef_id, uint32_t flags)
该函数用于设置由参数 flags 指定的事件标志,这些标志位于由参数ef_id指定的事件标志对象中。
处于阻塞状态的具有最高优先级的线程将被通知恢复执行。函数返回存储在事件控制块中的事件标志或错误代码(最高位设置,参见标志函数错误代码)。当给 osEventFlagsWait 调用提供选项 osFlagsNoClear 时,可能会按优先级顺序唤醒更多线程。
| 参数 | 类型 | |
|---|---|---|
| ef_id | 输入 | 通过 osEventFlagsNew 获取的事件标志 ID。 |
| flags | 输入 | 指定应设置的标志。 |
| 返回值 | 描述 |
|---|---|
| 设置后的事件标志 | 成功后。 |
| osFlagsErrorUnknown | 未指定错误。 |
| osFlagsErrorParameter | 参数 ef_id 未识别为有效的事件标志对象,或flags的最高位设置。 |
| osFlagsErrorResource | 事件标志对象处于无效状态。 |
- uint32_t osEventFlagsClear(osEventFlagsId_t ef_id, uint32_t flags)
该函数用于清除由参数 flags 指定的事件标志,这些标志位于由参数ef_id指定的事件标志对象中。函数返回清除前的事件标志或错误代码。
| 参数 | 类型 | |
|---|---|---|
| ef_id | 输入 | 通过 osEventFlagsNew 获取的事件标志 ID。 |
| flags | 输入 | 指定应清除的标志。 |
| 返回值 | 描述 |
|---|---|
| 清除前的事件标志 | 成功时。 |
| osFlagsErrorUnknown | 未指定错误。 |
| osFlagsErrorParameter | 参数 ef_id 未识别为有效的事件标志对象,或flags 的最高位设置。 |
| osFlagsErrorResource | 事件标志对象处于无效状态。 |
- uint32_t osEventFlagsGet(osEventFlagsId_t ef_id)
该函数返回由参数 ef_id 指定的事件标志对象中当前设置的事件标志,或在出现错误时返回 0。
| 参数 | 类型 | |
|---|---|---|
| ef_id | 输入 | 通过 osEventFlagsNew 获取的事件标志 ID。 |
| 返回值 | 描述 |
|---|---|
| 当前事件标志 | 成功后。 |
| 0 | 错误时。 |
- uint32_t osEventFlagsWait (osEventFlagsId_t ef_id, uint32_t flags, uint32_t options, uint32_t timeout)
该函数暂停当前运行中的线程,直到由参数 flags 指定的任意或全部事件标志在由参数 ef_id 指定的事件对象中被设置。如果这些事件标志已经被设置,函数会立即返回。否则,线程会进入阻塞状态。
options参数指定等待条件:
| 选项值 | 等待条件 |
|---|---|
| osFlagsWaitAny | 等待任意标志(默认)。 |
| osFlagsWaitAll | 等待所有标志。 |
| osFlagsNoClear | 不清除已指定等待的标志。 |
如果在 options 中设置了 osFlagsNoClear,可以使用osEventFlagsClear手动清除标志。
timeout 参数指定系统等待事件标志的时间。在等待期间,调用此函数的线程会进入阻塞状态。timeout 参数可以有以下值:
- 当 timeout 为 0 时,函数立即返回(即尝试行为)。
- 当 timeout 设置为 osWaitForever 时,函数将无限期等待直到事件标志变为可用(即等待行为)。
- 所有其他值指定超时的内核滴答数(即定时等待行为)。
| 参数 | 类型 | |
|---|---|---|
| ef_id | 输入 | 通过 osEventFlagsNew 获取的事件标志 ID。 |
| flags | 输入 | 指定要等待的标志。 |
| options | 输入 | 指定标志选项(osFlagsXxxx)。 |
| timeout | 输入 | 超时值,或在无超时的情况下为 0。 |
| 返回值 | 描述 |
|---|---|
| 清除前的事件标志 | 成功时。 |
| osFlagsErrorUnknown | 未指定错误。 |
| osFlagsErrorTimeout | 在给定时间内未设置所等待的标志。 |
| osFlagsErrorParameter | 参数 ef_id 未识别为有效的事件标志对象,或flags 的最高位设置。 |
| osFlagsErrorResource | 在未指定超时时未设置所等待的标志。 |
- osStatus_t osEventFlagsDelete(osEventFlagsId_t ef_id)
该函数删除由参数 ef_id 指定的事件标志对象,并释放用于事件标志处理的内部内存。此调用后,ef_id 不再有效且不能使用。这可能导致等待此事件对象标志的线程饥饿。ef_id 可以使用函数 osEventFlagsNew 再次创建。
| 参数 | 类型 | |
|---|---|---|
| ef_id | 输入 | 通过 osEventFlagsNew 获取的事件标志 ID。 |
| 返回值 | 描述 |
|---|---|
| osOK | 指定的事件标志对象已被删除。 |
| osErrorISR | osEventFlagsDelete 不能从中断服务例程调用。 |
| osFlagsErrorParameter | 参数 ef_id 为 NULL 或无效。 |
| osFlagsErrorResource | 事件标志对象处于无效状态。 |
- const char * osEventFlagsGetName(osEventFlagsId_t ef_id)
该函数返回由参数 ef_id 标识的事件标志对象的名称字符串的指针,或在出现错误时返回 NULL。
| 参数 | 类型 | |
|---|---|---|
| ef_id | 输入 | 通过 osEventFlagsNew 获取的事件标志 ID。 |
| 返回值 | 描述 |
|---|---|
| 返回名称的以null结尾的字符串 | 成功时。 |
| NULL | 错误时。 |
该 API 目前不支持。
通用等待功能
通用等待功能提供了时间延迟的手段。
函数
- osStatus_t osDelay(uint32_t ticks)
该函数等待由内核滴答数指定的时间段。对于值为 1 的情况,系统将等待直到下一个定时器滴答发生。实际的时间延迟可能比指定的少一个定时器滴答,即如果在下一个系统滴答发生之前立即调用 osDelay(1),线程将立即被重新调度。
被延迟的线程将进入阻塞状态,并立即发生上下文切换。在给定的滴答数经过后,线程自动回到就绪状态。如果线程在就绪状态下具有最高优先级,它将立即被调度。
| 参数 | 类型 | |
|---|---|---|
| ticks | 输入 | 时间滴答值。 |
| 返回值 | 描述 |
|---|---|
| osOK | 时间延迟已执行。 |
| osErrorParameter | 时间不能被处理(零值)。 |
| osErrorISR | osDelay不能从中断服务例程调用。 |
| osError | osDelay 不能执行(内核未运行或不存在就绪线程)。 |
- osStatus_t osDelayUntil(uint32_t ticks)
该函数等待直到达到绝对时间(以内核滴答数指定)。
当内核滴答计数器溢出时,osDelayUntil 会处理这种边界情况。因此,提供一个低于当前滴答值的值是完全合法的,例如由 osKernelGetTickCount返回的值。通常作为用户,不必关心溢出。唯一需要记住的限制是最大延迟限制为 (2^31)-1 滴答。
被延迟的线程将进入阻塞状态,并立即发生上下文切换。在达到给定时间时,线程自动回到就绪状态。如果线程在就绪状态下具有最高优先级,它将立即被调度。
| 参数 | 类型 | |
|---|---|---|
| ticks | 输入 | 绝对时间滴答数。 |
| 返回值 | 描述 |
|---|---|
| osOK | 时间延迟已执行。 |
| osErrorParameter | 时间不能被处理(超出范围)。 |
| osErrorISR | osDelayUntil 不能从中断服务例程调用。 |
| osError | osDelayUntil 不能执行(内核未运行或不存在就绪线程)。 |
定时器管理
除了通用等待功能外,CMSIS-RTOS 还支持虚拟定时器对象。这些定时器对象可以触发函数的执行(而不是线程)。当定时器到期时,将执行回调函数以运行与定 时器相关的代码。每个定时器都可以配置为一次性定时器或周期性定时器。周期性定时器将重复其操作,直到被删除或停止。所有定时器都可以被启动、重启或停止。
定时器管理功能不能从中断服务例程中调用。
下图展示了周期性定时器的行为。对于一次性定时器,回调函数执行后定时器将停止。

使用CMSIS-RTOS软件定时器需要执行以下步骤:
- 定义定时器对象:
osTimerId_t one_shot_id, periodic_id;
- 定义定时器回调函数:
static void one_shot_Callback (void *argument) {
int32_t arg = (int32_t)argument; // cast back argument '0'
// do something, i.e. set thread/event flags
}
static void periodic_Callback (void *argument) {
int32_t arg = (int32_t)argument; // cast back argument '5'
// do something, i.e. set thread/event flags
}
- 实例化并启动定时器:
// creates a one-shot timer:
one_shot_id = osTimerNew(one_shot_Callback, osTimerOnce, (void *)0,
NULL); // (void*)0 is passed as an argument
// to the callback function
// creates a periodic timer:
periodic_id = osTimerNew(periodic_Callback, osTimerPeriodic, (void
*)5, NULL); // (void*)5 is passed as an argument
// to the callback function
osTimerStart(one_shot_id, 500U);
osTimerStart(periodic_id, 1500U);
// start the one-shot timer again after it has triggered the first
time:
osTimerStart(one_shot_id, 500U);
// when timers are not needed any longer free the resources:
osTimerDelete(one_shot_id);
osTimerDelete(periodic_id);
数据结构
- struct osTimerAttr_t
| 数据字段 | - | 描述 |
|---|---|---|
| const char * | name | 定时器的名称。一个指向具有人类可读名称的常量字符串的指针(在调试期间显示)。Default: NULL no name specified。 |
| uint32_t | attr_bits | 属性位。保留供将来使用(为了将来兼容性必须设置为 '0')。 |
| void * | cb_mem | 控制块内存。指向定时器控制块对象的内存的指针。默认值:NULL 表示使用自动动态分配的方式为定时器控制块分配内存。 |
| uint32_t | cb_size | 提供的控制块内存大小。通过 cb_mem 传递的内存块的大小(以字节为单位)。默认值:0 表示默认情况下没有为控制块 (cb_mem) 提供内存。 |
类型定义
- osTimerId_t
定时器 ID类型,用于标识一个定时器对象。
- void( osTimerFunc_t)(void argument)
定时器回调函数,每次定时器到期时都会调用。回调可能在专用定时器线程或中断上下文中执行,因此建议仅在定时器回调中使用 ISR 可调用函数。
| 参数 | 类型 | 描述 |
|---|---|---|
| argument | 输入 | 传递给osTimerNew的参数。 |
枚举类型
- enum osTimerType_t
指定 osTimerNew 中创建的定时器类型,包括一次性(osTimerOnce)和周期(osTimerPeriodic)两种。
| 枚举器 | 描述 |
|---|---|
| osTimerOnce | 一次性定时器。定时器一旦到期就不会自动重新启动,可以根据需要使用 osTimerStart 手动重新启动。 |
| osTimerPeriodic | 重复定时器。定时器会自动重复,并在运行时连续触发回调。 |
函数
- osTimerId_t osTimerNew(osTimerFunc_t func, osTimerType_t type, voidargument, const osTimerAttr_t attr)
函数 osTimerNew 用于创建一个一次性或周期性定时器,并将其与一个带参数的回调函数关联。定时器在使用 osTimerStart 函数启动之前处于停止状态。该函数可以在 RTOS 启动之前(调用 osKernelStart)安全地调用,但不能在它初始化之前(调用 osKernelInitialize)调用。
函数 osTimerNew 在成功时返回指向定时器对象标识符的指针,如果出现错误,则返回 NULL。
| 参数 | 类型 | 描述 |
|---|---|---|
| func | 输入 | 指向回调函数的函数指针。 |
| type | 输入 | osTimerOnce 表示一次性定时器,osTimerPeriodic 表示周期性定时器。 |
| argument | 输入 | 传递给定时器回调函数的参数。 |
| attr | 输入 | 定时器属性;如果为 NULL,则使用默认值。 |
| 返回值 | 描述 |
|---|---|
| 定时器ID | 成功时。 |
| NULL | 出错时。 |
- const char *osTimerGetName(osTimerId_t timer_id)
该函数返回由参数 timer_id 标识的定时器的名称字符串的指针,或在出现错误时返回 NULL。
| 参数 | 类型 | 描述 |
|---|---|---|
| timer_id | 输入 | 通过 osTimerNew 获取的定时器 ID。 |
| 返回值 | 描述 |
|---|---|
| 返回定时器名称的以null结尾的字符串 | 成功时。 |
| NULL | 出错时。 |
- osStatus_t osTimerStart(osTimerId_t timer_id, uint32_t ticks)
该函数启动或重启由参数 timer_id 指定的定时器。参数 ticks 指定定时器的时间滴答值。
| 参数 | 类型 | 描述 |
|---|---|---|
| timer_id | 输入 | 通过 osTimerNew 获取的定时器 ID。 |
| ticks | 输入 | 定时器的时间滴答值。 |
| 返回值 | 描述 |
|---|---|
| osOK | 指定的定时器已启动或重启。 |
| osErrorISR | osTimerStart 不能从中断服务例程调用。 |
| osErrorParameter | 参数 timer_id 为 NULL 或无效,或者 ticks不正确。 |
| osErrorResource | 定时器处于无效状态。 |
| osErrorNeedSched | 当前线程将被抢占。 |
- osStatus_t osTimerStop(osTimerId_t timer_id)
该函数停止由参数 timer_id 指定的定时器。
| 参数 | 类型 | 描述 |
|---|---|---|
| timer_id | 输入 | 通过 osTimerNew 获取的定时器 ID。 |
| 返回值 | 描述 |
|---|---|
| osOK | 指定的定时器已停止。 |
| osErrorISR | osTimerStop 不能从中断服务例程调用。 |
| osErrorParameter | 参数 timer_id 为 NULL 或无效。 |
| osErrorResource | 定时器未运行(只能停止正在运行的定时器)。 |
| osErrorNeedSched | 当前线程将被抢占。 |
- uint32_t osTimerIsRunning (osTimerId_t timer_id)
该函数检查由参数 timer_id 指定的定时器是否正在运行。如果定时器正在运行,则返回 1;如果定时器已停止或发生错误,则返回 0。
| 参数 | 类型 | 描述 |
|---|---|---|
| timer_id | 输入 | 通过 osTimerNew 获取的定时器 ID。 |
| 返回值 | 描述 |
|---|---|
| 0 | 定时器已停止。 |
| 1 | 定时器正在运行。 |
- osStatus_t osTimerDelete(osTimerId_t timer_id)
该函数删除由参数 timer_id 指定的定时器。
| 参数 | 类型 | 描述 |
|---|---|---|
| timer_id | 输入 | 通过 osTimerNew 获取的定时器 ID。 |
| 返回值 | 描述 |
|---|---|
| osOK | 指定的定时器已被删除。 |
| osErrorISR | osTimerDelete 不能从中断服务例程调用。 |
| osErrorParameter | 参数 timer_id 为 NULL 或无效。 |
| osErrorResource | 定时器处于无效状态。 |
互斥锁管理
互斥锁(通常称为 Mutex)在各种操作系统中用于资源管理。在微控制器设备中,许多资源可以被反复使用,但在同一时间只能由一个线程使用(例如通信通道、内存和文件)。互斥锁用于保护对共享资源的访问。一个互斥锁被创建后,可以在不同的线程之间传递(它们可以获取和释放互斥锁)。

互斥锁是信号量的一种特殊形式。与信号量相似,它是一个令牌的容器。但与可以拥有多个令牌的信号量不同,互斥锁只能携带一个令牌(代表资源)。因此,互斥锁令牌是二元的且有界的,即它要么是可用的,要么被拥有线程阻塞。互斥锁的优势在于它引入了线程所有权的概念。当一个线程获取互斥锁并成为其所有者时,来自该线程的后续互斥锁获取将立即成功,无需任何延迟(如果指定了osMutexRecursive)。因此,互斥锁的获取和释放可以嵌套。

与二进制信号量不同,后者可以从中断服务例程 (ISR) 中释放,互斥锁管理函数不能在中断服务例程 (ISR) 中调用。
数据结构
- struct osMutexAttr_t
| 数据字段 | - | 描述 |
|---|---|---|
| const char * | name | 互斥锁的名称。指向一个常量字符串,用于在调试时显示互斥量对象的可读名称。默认值: NULL,表示未指定对象名称。 |
| uint32_t | attr_bits | 属性位。可以使用以下位掩码来设置选项:osMutexRecursive: 线程可以多次获取互斥锁而不会锁定自己。osMutexPrioInherit(*):拥有线程继承(更高优先级的)等待线程的优先级。osMutexRobust(*):当拥有线程终止时,互斥锁会自动释放。使用逻辑“或”运算来选择多个选项,例如:osMutexRecursive / osMutexPrioInherit; 默认值: 0,表示:非递归互斥量:线程不能多次获取同一互斥量。非优先级继承:所有者线程优先级不会改变。互斥量不自动释放:所有者线程终止时,互斥量对象不会自动释放。(*):不支持该选项 |
| void * | cb_mem | 控制块内存指针。指向用于存放互斥量控制块对象的内存区域。默认值: NULL,表示使用自动动态内存分配方式分配控制块内存。 |
| uint32_t | cb_size | 控制块内存大小。通过 cb_mem 参数传入的内存区域大小(字节)。默认值: 0,表示未提供cb_mem 内存区域。 |
宏定义
- #define osMutexRecursive 0x00000001U
osMutexAttr_t中的递归标志。
允许同一个线程多次获取互斥锁而不会造成死锁。每次互斥锁被获取时,锁计数会增加。互斥锁必须被释放相同的次数直到锁计数归零,此时互斥锁实际上被释放,其他线程可以获取它。递归锁的最大数量取决于实现,即用于锁计数的数据类型的大小。如果递归锁的最大数量耗尽,互斥锁的获取可能会失败。
- #define osMutexPrioInherit 0x00000002U
osMutexAttr_t中的优先级继承标志。
使用优先级继承协议的互斥锁会将等待线程的优先级传递给当前的互斥锁所有者,如果所有者线程的优先级较低。这确保低优先级线程不会阻塞高优先级线程。
否则,低优先级线程可能持有互斥锁但不会被执行,因为另一个中优先级线程正在运行。没有优先级继承,高优先级线程等待互斥锁会被中优先级线程阻塞,这种情况被称为优先级反转。
目前此标志不受支持。
- #define osMutexRobust 0x00000008U
osMutexAttr_t中的健壮标志。
健壮的互斥锁在所有者线程被终止时(通过osThreadExit或osThreadTerminate)会自动释放。非健壮的互斥锁不会自动释放,用户必须手动确保互斥锁的释放。
目前此标志不受支持。
类型定义
- osMutexId_t
互斥锁 ID 用于标识互斥锁。
函数
- osMutexId_t osMutexNew(const osMutexAttr_t *attr)
该函数创建并初始化一个新的互斥锁对象,并返回指向互斥锁对象标识符的指针,或在出现错误时返回 NULL。在 RTOS 启动之前(调用osKernelStart)可以安全地调用该函数,但在初始化之前(调用osKernelInitialize)不能调用。
参数 attr 设置互斥锁对象的属性(参考 osMutexAttr_t)。如果设置为NULL,则使用默认属性。
| 参数 | 类型 | 描述 |
|---|---|---|
| attr | 输入 | 互斥锁属性;如果为 NULL,则使用默认值。 |
| 返回值 | 描述 |
|---|---|
| 互斥锁ID | 成功时。 |
| NULL | 出错时。 |
- const char *osMutexGetName(osMutexId_t mutex_id)
该函数返回由参数 mutex_id 标识的互斥锁的名称字符串的指针,或在出现错误时返回 NULL。
| 参数 | 类型 | 描述 |
|---|---|---|
| mutex_id | 输入 | 通过 osMutexNew 获取的互斥锁 ID。 |
| 返回值 | 描述 |
|---|---|
| 返回互斥锁名称的以null结尾的字符串 | 成功时。 |
| NULL | 出错时。 |
该 API 目前不支持。
- osStatus_t osMutexAcquire(osMutexId_t mutex_id, uint32_t timeout)
该阻塞函数等待由参数 mutex_id 指定的互斥锁对象变为可用。如果没有其他线程获取了互斥锁,函数立即返回并阻塞互斥锁对象。
参数 timeout 指定系统等待获取互斥锁的时间。在等待期间,调用此函数的线程会进入阻塞状态。参数 timeout 可以有以下值:
- 当 timeout 为 0 时,函数立即返回(即尝试行为)。
- 当 timeout 设置为 osWaitForever 时,函数将无限期等待直到互斥锁变为可用(即等待行为)。
- 所有其他值指定超时的内核滴答数(即定时等待行为)。
| 参数 | 类型 | 描述 |
|---|---|---|
| mutex_id | 输入 | 通过 osMutexNew 获取的互斥锁 ID。 |
| timeout | 输入 | 超时值,或在无超时的情况下为 0。 |
| 返回值 | 描述 |
|---|---|
| osOK | 互斥锁已被获取。 |
| osErrorTimeout | 在给定时间内无法获取互斥锁。 |
| osErrorISR | 不能从中断服务例程调用。 |
| osErrorParameter | 参数 mutex_id 为 NULL 或无效。 |
| osErrorResource | 在未指定超时时无法获取互斥锁。 |
- osStatus_t osMutexRelease(osMutexId_t mutex_id)
该函数释放由参数 mutex_id 指定的互斥锁。当前等待此互斥锁的其他线程将被置于就绪状态。
| 参数 | 类型 | 描述 |
|---|---|---|
| mutex_id | 输入 | 通过 osMutexNew 获取的互斥锁 ID。 |
| 返回值 | 描述 |
|---|---|
| osOK | 互斥锁已正确释放。 |
| osErrorISR | 不能从中断服务例程调用。 |
| osErrorParameter | 参数 mutex_id 为 NULL 或无效。 |
| osErrorResource | 无法释放互斥锁(互斥锁未被获取或运行线程不是所有者)。 |
- osThreadId_t osMutexGetOwner(osMutexId_t mutex_id)
该函数返回获取由参数 mutex_id 指定的互斥锁的线程的线程 ID。在出错的情况下或如果互斥锁没有被任何线程阻塞,它返回 NULL。
| 参数 | 类型 | 描述 |
|---|---|---|
| mutex_id | 输入 | 通过 osMutexNew 获取的互斥锁 ID。 |
| 返回值 | 描述 |
|---|---|
| 返回所有者线程的线程ID | 成功时。 |
| NULL | 如果互斥锁未被获取。 |
- osStatus_t osMutexDelete(osMutexId_t mutex_id)
该函数删除由参数 mutex_id 指定的互斥锁对象。它释放用于互斥锁处理的内部内存。此调用后,mutex_id 不再有效且不能使用。可以使用函数osMutexNew再次创建互斥锁。
| 参数 | 类型 | 描述 |
|---|---|---|
| mutex_id | 输入 | 通过 osMutexNew 获取的互斥锁 ID。 |
| 返回值 | 描述 |
|---|---|
| osOK | 互斥锁对象已被删除。 |
| osErrorISR | 不能从中断服务例程调用。 |
| osErrorParameter | 参数 mutex_id 为 NULL 或无效。 |
信号量
信号量用于管理和保护对共享资源的访问。信号量与互斥锁非常相似。不同的是,互斥锁一次只允许一个线程访问共享资源,而信号量可以用来允许一定数量的线程或中断服务例程 (ISR) 访问一组共享资源。通过使用信号量,可以有效地管理对一组相同外设的访问(例如多个 DMA 通道)。

信号量对象应该初始化为可用令牌的最大数量。这个可用资源的数量是作为osSemaphoreNew 函数的参数指定的。每次通过 osSemaphoreAcquire 获取一个信号量令牌(处于可用状态),信号量的计数就会减少。
当信号量的计数为 0(即信号量耗尽状态)时,就无法再获取更多的信号量令牌。尝试获取信号量令牌的线程或中断服务例程 (ISR) 需要等待,直到下一个令牌释放。通过 osSemaphoreRelease 释放信号量会增加信号量的计数。

函数 osSemaphoreAcquire、osSemaphoreGetCount 和 osSemaphoreRelease可以在中断服务例程中调用。
数据结构
- struct osSemaphoreAttr_t
| 数据字段 | - | 描述 |
|---|---|---|
| const char * | name | 信号量的名称。一个指向具有易读名称的常量字符串的指针(在调试期间显示)。默认情况下,此指针为 NULL,表示未为信号量对象分配名称。 |
| uint32_t | attr_bits | 属性位。保留供将来使用(为了将来兼容性必须设置为 '0')。 |
| void * | cb_mem | 控制块内存指针。指向信号量控制块对象的内存的指针。默认值:NULL,表示使用自动动态分配的方式为信号量控制块分配内存。 |
| uint32_t | cb_size | 提供的控制块内存大小,通过 cb_mem 传递的内存块的大小(以字节为单位)。默认值:0,表示默认情况下没有为控制块 (cb_mem) 提供内存。 |
类型定义
- osSemaphoreId_t
信号量 ID 用于标识信号量。
函数
- osSemaphoreId_t osSemaphoreNew(uint32_t max_count, uint32_t initial_count, const osSemaphoreAttr_t *attr)
该函数创建并初始化一个用于管理对共享资源的访问的信号量对象,并返回指向信号量对象标识符的指针,或在出现错误时返回 NULL。在 RTOS 启动之前(调用 osKernelStart)可以安全地调用该函数,但在初始化之前(调用 osKernelInitialize)不能调用。
参数 max_count 指定可用令牌的最大数量。max_count 值为 1 创建一个二进制信号量。
参数 initial_count 设置可用令牌的初始数量。
参数 attr 指定额外的信号量属性。如果设置为 NULL,则使用默认属性。
| 参数 | 类型 | |
|---|---|---|
| max_count | 输入 | 可用令牌的最大数量。 |
| initial_count | 输入 | 可用令牌的初始数量。 |
| attr | 输入 | 信号量属性;如果为 NULL,则使用默认值。 |
| 返回值 | 描述 |
|---|---|
| semaphore ID | 成功时。 |
| NULL | 出错时。 |
- const char * osSemaphoreGetName(osSemaphoreId_t semaphore_id)
该函数返回由参数 semaphore_id 标识的信号量的名称字符串的指针,或在出现错误时返回 NULL。
| 参数 | 类型 | |
|---|---|---|
| semaphore_id | 输入 | 通过 osSemaphoreNew 获取的信号量 ID。 |
| 返回值 | 描述 |
|---|---|
| name as null-terminated string | 成功时。 |
| NULL | 出错时。 |
该 API 目前不支持。
- osStatus_t osSemaphoreAcquire(osSemaphoreId_t semaphore_id, uint32_t timeout)
该阻塞函数等待由参数 semaphore_id 指定的信号量对象的一个令牌变为可用。如果令牌可用,函数立即返回并递减令牌计数。
参数 timeout 指定系统等待获取令牌的时间。在等待期间,调用此函数的线程会进入阻塞状态。参数 timeout 可以有以下值:
- 当 timeout 为 0 时,函数立即返回(即尝试行为)。
- 当 timeout 设置为 osWaitForever 时,函数将无限期等待直到信号量变为可用(即等待行为)。
- 所有其他值指定超时的内核滴答数(即定时等待行为)。
如果参数 timeout 设置为 0,则可以在中断服务例程中调用。
| 参数 | 类型 | |
|---|---|---|
| semaphore_id | 输入 | 通过 osSemaphoreNew 获取的信号量 ID。 |
| timeout | 输入 | 超时值,或在无超时的情况下为 0。 |
| 返回值 | 描述 |
|---|---|
| osOK | 令牌已被获取并且令牌计数递减。 |
| osErrorTimeout | 在给定时间内无法获取令牌。 |
| osErrorParameter | 参数 semaphore_id 为 NULL 或无效。 |
| osErrorResource | 在未指定超时时无法获取令牌。 |
- osStatus_t osSemaphoreRelease (osSemaphoreId_t semaphore_id)
该函数释放由参数 semaphore_id 指定的信号量对象的一个令牌。令牌只能被释放到创建时指定的最大计数,参见 osSemaphoreNew。当前等待此信号量对象的令牌的其他线程将被置于就绪状态。
| 参数 | 类型 | |
|---|---|---|
| semaphore_id | 输入 | 通过 osSemaphoreNew 获取的信号量 ID。 |
| 返回值 | 描述 |
|---|---|
| osOK | 令牌已被释放并且计数递增。 |
| osErrorParameter | 参数 semaphore_id 为 NULL 或无效。 |
| osErrorResource | 无法释放令牌(已达到最大令牌计数)。 |
| osErrorNeedSched | 当前线程将被抢占。 |
- uint32_t osSemaphoreGetCount(osSemaphoreId_t semaphore_id)
该函数返回由参数 semaphore_id 指定的信号量对象的可用令牌数量。在出错的情况下返回 0。
| 参数 | 类型 | |
|---|---|---|
| semaphore_id | 输入 | 通过 osSemaphoreNew 获取的信号量 ID。 |
| 返回值 | 描述 |
|---|---|
| number of tokens available. | 成功时。 |
| 0 | 出错时。 |
- osStatus_t osSemaphoreDelete(osSemaphoreId_t semaphore_id)
该函数删除由参数 semaphore_id 指定的信号量对象。它释放用于信号量处理的内部内存。此调用后,semaphore_id 不再有效且不能使用。可以使用函数 osSemaphoreNew 再次创建信号量。
| 参数 | 类型 | |
|---|---|---|
| semaphore_id | 输入 | 通过 osSemaphoreNew 获取的信号量 ID。 |
| 返回值 | 描述 |
|---|---|
| osOK | 信号量对象已被删除。 |
| osErrorParameter | 参数 semaphore_id 为 NULL 或无效。 |
| osErrorISR | 不能从中断服务例程调用。 |
消息队列
消息传递是线程之间另一种基本的通信方式。在消息传递模型中,一个线程显式地发送数据,而另一个线程接收它。这种操作更像是某种输入/输出(I/O)操作,而不是直接访问要共享的信息。在 CMSIS-RTOS 中,这种机制被称为消息队列。数据以类似先进先出(FIFO)的方式从一个线程传递到另一个线程。使用消息队列函数,你可以控制、发送、接收或等待消息。要传递的数据可以是整数或指针类型:

函数osMessageQueuePut、osMessageQueueGet、osMessageQueueGetCapacity、osMessageQueueGetMsgSize、osMessageQueueGetCount、osMessageQueueGetSpace 可以在中断服务例程中调用。
数据结构
- struct osMessageQueueAttr_t
| 数据字段 | - | 描述 |
|---|---|---|
| const char * | name | 消息队列的名称。一个指向具有易读名称的常量字符串的指针(在调试期间显示)。默认情况下,此指针为 NULL,表示未为消息队列对象分配名称。 |
| uint32_t | attr_bits | 属性位。保留供将来使用(为了将来兼容性必须设置为 '0')。 |
| void * | cb_mem | 控制块内存指针。指向消息队列控制块对象的内存的指针。默认值:NULL,表示使用自动动态分配的方式为消息队列控制块分配内存。 |
| uint32_t | cb_size | 提供的控制块内存大小,通过 cb_mem 传递的内存块的大小(以字节为单位)。默认值:0,表示默认情况下没有为控制块 (cb_mem) 提供内存。 |
| void * | mq_mem | 数据存储内存,指向消息队列数据的内存的指针。默认值:NULL,表示使用自动动态分配的方式为内存池数据分配内存。 |
| uint32_t | mq_size | 提供的数据存储内存大小,通过 mq_mem传递的内存块的大小(以字节为单位)。最小内存块大小为osMessageQueueNew 函数的参数msg_count * msg_size。msg_size 被四舍五入到一个双倍偶数以确保内存块的 32位对齐。默认值:0,表示默认情况下没有为数据存储 (mq_mem) 提供内存。 |
函数
- osMessageQueueId_t osMessageQueueNew(uint32_t msg_count, uint32_t msg_size, const osMessageQueueAttr_t *attr)
该函数创建并初始化一个消息队列对象,并返回消息队列对象标识符,或在出现错误时返回 NULL。
函数可以在内核初始化后通过 osKernelInitialize 调用。在 RTOS 内核启动之前通过 osKernelStart 可以创建消息队列对象。
消息队列数据所需的总内存至少为 msg_count * msg_size。msg_size 被四舍五入到一个双倍偶数以确保内存块的 32 位对齐。
从消息队列分配的内存块具有用参数 msg_size 定义的固定大小。
| 参数 | 类型 | 描述 |
|---|---|---|
| msg_count | 输入 | 队列中的最大消息数。 |
| msg_size | 输入 | 字节中的最大消息大小。 |
| attr | 输入 | 消息队列属性;如果为 NULL,则使用默认值。 |
| 返回值 | 描述 |
|---|---|
| message queue ID | 成功时。 |
| NULL | 出错时。 |
- const char *osMessageQueueGetName (osMessageQueueId_t mq_id)
该函数返回由参数 mq_id 标识的消息队列的名称字符串的指针,或在出现错误时返回 NULL。
| 参数 | 类型 | 描述 |
|---|---|---|
| mq_id | 输入 | 通过 osMessageQueueNew 获取的消息队列ID。 |
| 返回值 | 描述 |
|---|---|
| name as null-terminated string | 成功时。 |
| NULL | 出错时。 |
该 API 目前不支持。
- osStatus_t osMessageQueuePut(osMessageQueueId_t mq_id, const void *msg_ptr, uint8_t msg_prio, uint32_t timeout)
阻塞函数 osMessageQueuePut 将由 msg_ptr 指向的消息放入参数mq_id 指定的消息队列中。参数 msg_prio 用于按照消息的优先级(数字越大表示优先级越高)进行排序。
参数 timeout 指定系统等待将消息放入队列的时间。在系统等待期间,调用此函数的线程将进入阻塞状态。参数 timeout 可以有以下值:
- 当 timeout 为 0 时,函数立即返回(即尝试行为)。
- 当 timeout 设置为 osWaitForever 时,函数将无限期等待,直到消息被传递(即等待行为)。
- 所有其他值指定了一个以内核滴答为单位的超时时间(即定时等待行为)。
如果参数 timeout 设置为 0,则可以从中断服务程序中调用。
| 参数 | 类型 | 描述 |
|---|---|---|
| mq_id | 输入 | 通过 osMessageQueueNew 获取的消息队列ID。 |
| msg_ptr | 输入 | 指向要放入队列的消息的缓冲区的指针。 |
| msg_prio | 输入 | 消息优先级。 |
| timeout | 输入 | 超时值,或者在没有超时的情况下为 0。 |
| 返回值 | 描述 |
|---|---|
| osOK | 消息已放入队列。 |
| osErrorTimeout | 在给定时间内无法将消息放入队列(等待定时行为)。 |
| osErrorResource | 队列中没有足够的空间(尝试行为)。 |
| osErrorParameter | 参数 mq_id 为 NULL 或无效,中断服务程序中指定了非零超时时间。 |
| osErrorNeedSched | 当前线程将被抢占。 |
- osStatus_t osMessageQueueGet(osMessageQueueId_t mq_id, void msg_ptr, uint8_t msg_prio, uint32_t timeout)
函数 osMessageQueueGet 从参数 mq_id 指定的消息队列中检索消息,并将其保存到参数 msg_ptr 指向的缓冲区中。如果非 NULL,则将消息优先级存储到参数 msg_prio 中。
参数 timeout 指定系统等待从队列中检索消息的时间。在系统等待期间,调用此函数的线程将进入阻塞状态。参数 timeout 可以有以下值:
- 当 timeout 为 0 时,函数立即返回(即尝试行为)。
- 当 timeout 设置为 osWaitForever 时,函数将无限期等待,直到消息被检索(即等待行为)。
- 所有其他值指定了一个以内核滴答为单位的超时时间(即定时等待行为)。
如果参数 timeout 设置为 0,则可以从中断服务程序中调用。
| 参数 | 类型 | 描述 |
|---|---|---|
| mq_id | 输入 | 通过 osMessageQueueNew 获取的消息队列ID。 |
| msg_ptr | 输出 | 用于从队列中获取消息的缓冲区的指针。 |
| msg_prio | 输出 | 用于消息优先级的缓冲区的指针,如果为NULL则不保存。 |
| timeout | 输入 | 超时值,或者在没有超时的情况下为 0。 |
| 返回值 | 描述 |
|---|---|
| osOK | 消息已从队列中检索。 |
| osErrorTimeout | 在给定时间内无法从队列中检索消息(定时等待行为)。 |
| osErrorResource | 队列中没有要获取的内容(尝试行为)。 |
| osErrorParameter | 参数 mq_id 为 NULL 或无效,中断服务程序中指定了非零超时时间。 |
- uint32_t osMessageQueueGetCapacity(osMessageQueueId_t mq_id)
函数 osMessageQueueGetCapacity 返回参数 mq_id 指定的消息队列对象中的最大消息数,或者在出错时返回 0。
| 参数 | 类型 | 描述 |
|---|---|---|
| mq_id | 输入 | 通过 osMessageQueueNew 获取的消息队列ID。 |
| 返回值 | 描述 |
|---|---|
| maximum number of messages | 成功时。 |
| 0 | 出错时。 |
- uint32_t osMessageQueueGetMsgSize(osMessageQueueId_t mq_id)
函数 osMessageQueueGetMsgSize 返回参数 mq_id 指定的消息队列对象的最大消息大小(以字节为单位),或者在出错时返回 0。
| 参数 | 类型 | 描述 |
|---|---|---|
| mq_id | 输入 | 通过 osMessageQueueNew 获取的消息队列ID。 |
| 返回值 | 描述 |
|---|---|
| maximum message size in bytes | 成功时。 |
| 0 | 出错时。 |
- uint32_t osMessageQueueGetCount(osMessageQueueId_t mq_id)
函数 osMessageQueueGetCount 返回参数 mq_id 指定的消息队列对象中排队的消息数,或者在出错时返回 0。
| 参数 | 类型 | 描述 |
|---|---|---|
| mq_id | 输入 | 通过 osMessageQueueNew 获取的消息队列ID。 |
| 返回值 | 描述 |
|---|---|
| number of queued messages | 成功时。 |
| 0 | 出错时。 |
- uint32_t osMessageQueueGetSpace(osMessageQueueId_t mq_id)
函数 osMessageQueueGetSpace 返回参数 mq_id 指定的消息队列对象中可用于消息的槽位数,或者在出错时返回 0。
| 参数 | 类型 | 描述 |
|---|---|---|
| mq_id | 输入 | 通过 osMessageQueueNew 获取的消息队列ID。 |
| 返回值 | 描述 |
|---|---|
| number of available slots for messages | 成功时。 |
| 0 | 出错时。 |
- osStatus_t osMessageQueueReset(osMessageQueueId_t mq_id)
函数 osMessageQueueReset 重置参数 mq_id 指定的消息队列。
| 参数 | 类型 | 描述 |
|---|---|---|
| mq_id | 输入 | 通过 osMessageQueueNew 获取的消息队列ID。 |
| 返回值 | 描述 |
|---|---|
| osOK | 消息队列已重置。 |
| osErrorParameter | 参数 mq_id 为 NULL 或无效。 |
| osErrorResource | 消息队列处于无效状态。 |
| osErrorISR | 不允许从中断服务程序中调用osMessageQueueReset。 |
- osStatus_t osMessageQueueDelete(osMessageQueueId_t mq_id)
函数 osMessageQueueDelete 删除参数 mq_id 指定的消息队列对象。它释放了为消息队列处理而获得的内部内存。调用此函数后,mq_id 不再有效,不能再使用。可以使用函数 osMessageQueueNew 再次创建消息队列。
| 参数 | 类型 | 描述 |
|---|---|---|
| mq_id | 输入 | 通过 osMessageQueueNew 获取的消息队列ID。 |
| 返回值 | 描述 |
|---|---|
| osOK | 消息队列对象已删除。 |
| osErrorParameter | 参数 mq_id 为 NULL 或无效。 |
| osErrorISR | 不允许从中断服务程序中调用osMessageQueueDelete。 |
定义
以下常量和枚举用于许多 CMSIS-RTOS 函数调用。
宏定义
(1) #define osWaitForever 0xFFFFFFFFU
一个特殊的超时值,通知 RTOS 无限等待直到资源可用。适用于以下函数:
- osDelay : 等待超时(时间延迟)。
- osThreadFlagsWait : 等待当前运行线程的一个或多个线程标志变为信号状态。
- osEventFlagsWait : 等待一个或多个事件标志变为信号状态。
- osMutexAcquire : 获取互斥锁或超时(如果锁已被锁定)。
- osSemaphoreAcquire : 获取信号量令牌或超时(如果没有可用的令牌)。
- osMessageQueuePut : 将消息放入队列或超时(如果队列已满)。
- osMessageQueueGet : 从队列获取消息或超时(如果队列为空)。
(2) #define osFlagsWaitAny 0x00000000U
参考:
- osEventFlagsWait
- osThreadFlagsWait
(3) #define osFlagsWaitAll 0x00000001U
参考:
- osEventFlagsWait
- osThreadFlagsWait
(4) #define osFlagsNoClear 0x00000002U
参考:
- osEventFlagsWait
- osThreadFlagsWait
枚举类型
- enum osStatus_t
osStatus_t枚举定义了许多 CMSIS-RTOS 函数返回的事件状态和错误代码。
| 枚举值 | 描述 |
|---|---|
| osOK | 操作成功完成。 |
| osError | 未指定的 RTOS 错误:运行时错误但没有其他错误消息适用。 |
| osErrorTimeout | 操作未在超时期间完成。 |
| osErrorResource | 资源不可用。 |
| osErrorParameter | 参数错误。 |
| osErrorNoMemory | 系统内存不足:无法为操作分配或保留内存。 |
| osErrorISR | 不允许在 ISR 上下文中:不能从中断服务例程中调用该函数。 |
| osErrorNeedSched | 需要重新调度,因为可以唤醒优先级更高的任务。 |
| osStatusReserved | 防止枚举向下大小编译器优化。 |
TlsrChannel使用指南
概述
TlsrChannel组件主要提供低功耗host-device方案,其主要功能是搭建一个host芯片(如Camera)与Wi-Fi芯片(device)通信的信道,该信道由两个子信道组成。
-
Msg通道:主要用于传输与接收用户自定义之消息。
-
Data通道:主要用于传输与接收网络数据包。
TlsrChannel软件运行架构如下图所示:

组件说明:
-
Wlan0:host端与device端皆会有wlan0网络接口,与正常网络接口无区别,主要用于接收/发送网络数据包。
-
Channel Adapt:透过host端sncmf_netlink以及device端FWIL模块做到封装/去除channel通信协议。
-
Repeater:Repeater可以区分收到的网络数据包转发给host或是device端,亦可转发给两端。
Channel使用指南
为了帮助用户快速将Channel组件与自身业务对接,本章节对Channel的基本使用规则做介绍,主要包括以下方面:
-
TlsrChannel组件初始化
-
待机唤醒配置
-
Repeater转发规则开发
-
API使用指南
TlsrChannel组件初始化
TlsrChannel组件初始化的目的主要是建立主芯片与从芯片(Wi-Fi)的通道,保证主、从芯片可以能够进行网络数据通讯,亦能进行用户自定义消息通讯,TlsrChannel组件初始化流程如下图所示。

Host侧:
-
sncmf init: 初始化整个sncmf模块。
-
sdio init: 初始化sdio相关资源。
-
netdev init: 初始化网络节点,用于接收传送网络数据包。
-
netlink init: netlink初始化,主要用于建立应用层与内核信息传递,初始化完成会传送消息sncmf set ready到TLSR9118。
device侧:
-
sdio init: 使sdio device处于等卡状态,以便sdio主设备与其建立连接,此流程在device启动流程便会自动执行。
-
register event cb: 建立网络节点网络属性变化的回调函数,并且启动TlsrChannel传送信息的功能。
-
TlsrChannel init: 主要功能为设置Repeater模块以及注册侧消息回调函数(Msg rx cb)。
- 设置Repeater: 此模块控制网络数据包转发规则, 详细过滤规则请参见章节Repeater转发规则开发。
- 注册回调函数: 注册TLSR9118侧接收消息回调函数,主要用于用户专属的业务开发。
-
wait host ready: TLSR9118侧会收到host ready通知,表示TlsrChannel通道已开通。
TlsrChannel初始化过程Demo如下:
步骤 1:注册网络节点网络属性变化的回调函数,并且启动TlsrChannel传送讯息的功能,对应API为tlsr_wifi_register_event_callback;
步骤 2:初始化TlsrChannel,对应API为tlsr_channel_init;
步骤 3:重置Repeater规则,对应API为tlsr_vlwip_netif_reset;
步骤 4:设置Repeater模块转发规则,对应Demo code函数为tlsr_channel_set_default_filter;
步骤 5:注册TLSR9118侧接收消息回调函数,对应API为tlsr_channel_register_rx_cb;
步骤 6:结束。
int tlsr_channel_init(void)
{
if (tlsr_vlwip_netif_reset(WIFI_FILTER_TYPE_IPV4) != WITS_OK) {
printf("%s: netif reset failed\n", __func__);
return WITS_FAIL;
}
if (tlsr_channel_set_default_filter() != WITS_OK) {
printf("%s: set_default_filter failed\n", __func__);
return WITS_FAIL;
}
tlsr_channel_dump_filter();
tlsr_channel_register_rx_cb(tlsr_channel_rx_callback);
printf("TlsrChannel init OK\n");
return WITS_OK;
}
int main(void)
{
int ret = WITS_OK;
char ifname[WIFI_IFNAME_MAX_SIZE + 1] = {0};
int len = sizeof(ifname);
tlsr_wifi_register_event_callback(event_handler, NULL);
tlsr_wifi_sta_start(ifname, &len);
#ifdef CONFIG_API_TLSRCHANNEL
tlsr_channel_init();
#endif
ret = tlsr_wifi_start_connect();
return ret;
}
待机唤醒配置(TBD)
Repeater转发规则开发
TlsrChannel组件里面的Repeater模块提供了网络转发功能。可以通过调用Repeater模块API来达到特定网络封包的转发方向。Host侧可以利用device侧的Repeater模块来过滤需要的网络封包,可以将不需要的网络封包交由device侧处理。
由于host与device共享相同的网络配置(MAC、IP 地址、netmask、getway),Channel组件一旦建立成功后,用户可以在host侧直接使用device侧网络接口向外网发送网络包。
对于发送到外部的网络包,host与device互不影响,统一都是通过device向外发送,发送行为不涉及转发规则。
对于网络收包行为,device侧收到网络封包会依据Repeater规则做转发到host或是device。
Device侧收到报文后的转发处理如图:

-
来自外部的ICMP、 IGMP、 ARP都会给host与device均转发一份,无需特别配置转发规则,Repeater预设以实现。
-
收到外部封包,但未匹配上任何转发规则,按默认规则进行转发(预设TlsrChannel已经设置为转发到host侧,用户端亦可透过API tlsr_wifi_set_default_filter作修改)。
-
接收到外部的封包,若匹配上转发规则(IP、协议类型、端口、端口范围),按照配置的转发方向转给对应的目标(host或device或两侧皆转)。
在Repeater初始化过程便会根据报文属性设置特定转发规则,转发配置以IPV4举例,配置转发规则的具体方法如下:
使用者依据业务需求的报文属性修改参数ipv4_filter_def_setting[],依据需求填写一至多组的filter数组参数。透过tlsr_wifi_add_filter函数会将每组filter新增至Repeater模块,其中需注意预设最多可以设定15组filter,若需要更多组数可更改config参数。
static struct wifi_ipv4_filter ipv4_filter_def_setting[] = {
/* UDP */
{
0, /* 未设定 */
7002, /* 设置对端端口号, 即报文携带的目的端口号需要为7002 */
0, /* 未设定 */
0, /* 未设定 */
0, /* 未设定 */
0, /* 未设定 */
0, /* 未设定 */
17, /* 设置报文类型需要为UDP报文, UDP为17, TCP为6 */
WIFI_FILTER_TO_LWIP, /* 配置匹配本转发规则的报文转发到device侧 */
WIFI_FILTER_MASK_REMOTE_PORT | WIFI_FILTER_MASK_PROTOCOL,
/* 掩码字段配置需要同时匹配对端端口号、协议类型 */
},
};
struct wifi_ipv4_filter结构体定义如下:
/* Data Filter Structure */
struct wifi_ipv4_filter {
unsigned int remote_ip; /* 选设字段, 指定接收报文携带的对端IP地址。即报文中的源IP地址*/
unsigned short local_port; /* 选设字段,指定接收报文对应的本端端口号。由于设备是接收方,即为对应报文携带的目的端口号*/
unsigned short localp_min; /* 选设字段,指定接收报文对应的本端端口范围最小值。由于设备是接收方,即为对应报文的目的端口范围最小值*/
unsigned short localp_max; /* 选设字段,指定接收报文对应的本端端口范围最大值。由于设备是接收方,即为对应报文的目的端口号范围最大值,最大值需大于最小值*/
unsigned short remote_port; /* 选设字段,指定接收报文对应的对端端口号。由于设备是接收方,即为对应报文携带的源端口号*/
unsigned short remotep_min; /* 选设字段,指定接收报文对应的对端端口范围最小值。由于设备是接收方,即为对应报文的源端口号范围最小值*/
unsigned short remotep_max; /* 选设字段,指定接收报文对应的对端端口范围最大值。由于设备是接收方,即为对应报文的源端口号范围最大值,最大值需大于最小值*/
unsigned char packet_type; /* 选设字段,指定接收报文采用的传输层协议,一般指定为TCP(6)或者UDP(17)*/
unsigned char config_type; /* 必设字段,设置匹配本转发规则的报文的转发方向, 设置为WIFI_FILTER_LWIP转发到device侧,设
置为WIFI_FILTER_VLWIP转发到主控侧, WIFI_FILTER_BOTH则两边都转发*/
unsigned char match_mask; /* 必设字段,指定上述哪些选设字段是有效值,每个选设字段通过对应Bit掩码置位。置位之后代表需要同时满足匹配该字段,例如 WIFI_FILTER_MASK_IP | WIFI_FILTER_MASK_PROTOCOL 值为0x1 | 0x2 = 0x3,表示报文需要同时满足匹配源IP地址和协议类型(TCP或UDP)才匹配本规则,匹配之后按照config_type指定的方向转发本报文 */
unsigned char resv; /* 保留字段 */
};
上述wifi_ipv4_filter结构中match_mask字段,通过掩码枚举值对应的组合来定义。
例如 “WIFI_FILTER_MASK_REMOTE_PORT | WIFI_FILTER_MASK_PROTOCOL” 表示报文需要同时匹配上指定的IP地址和对端端口号才算匹配本条规则。
针对wifi_filter_field_enum定义中源端口和目的端口的比对规则加以说明:
-
如果源端口号不匹配,则再去匹配指定的源端口号范围。
-
如果源端口号可以匹配, 则认为源端口号范围也已匹配。
对于同时匹配报文的目的端口号和目的端口号范围的场景也类似。
/* Data Filter Item */
typedef enum {
WIFI_FILTER_MASK_IP = 0x01, /* 掩码枚举:表示源IP地址,对应remote_ip字段 */
WIFI_FILTER_MASK_PROTOCOL = 0x02, /*掩码枚举:协议类型( TCP或UDP),对应packet_type字段,不指定则表示该字段不作为匹配条件 */
WIFI_FILTER_MASK_LOCAL_PORT = 0x04, /* 掩码枚举:接收到报文的目的端口号,对应local_port字段,不指定则表示该字段不作为匹配条件 */
WIFI_FILTER_MASK_LOCAL_PORT_RANGE = 0x08, /* 掩码枚举:接收到报文的目的端口号范围,对应localp_min和localp_max字段,不指定则表示该字段不作为匹配条件 */
WIFI_FILTER_MASK_REMOTE_PORT = 0x10, /* 掩码枚举:接收到报文的源端口号,对应remote_port字段,不指定则表示该字段不作为匹配条件*/
WIFI_FILTER_MASK_REMOTE_PORT_RANGE = 0x20, /*掩码枚举:接收到报文的源端口号范围,对应remotep_min和remotep_max字段,不指定则表示该字段不作为匹配条 */
WIFI_FILTER_MASK_BUTT
} wifi_filter_field_enum;```
在范例应用中, 预设DHCP封包传送至lwip端,并且通过网络事件变化接口更新DHCP资讯至host端, 如果服务器提供独立端口专供device侧进行业务交互,则可利用远端端口号作为转发规则识别并转发来自该服务器的报文到device侧。用户可以利用Repeater规则将适当的任务分配至host或device侧, 从而实现省电效果。
DHCP转发规则配置如下:
static struct wifi_ipv4_filter ipv4_filter_def_setting[] = {
/* DHCP */
{
0, /* remote ip */
68, /* local port */
0, /* localp_min */
0, /* localp_max */
0, /* remote_port */
0, /* remotep_min */
0, /* remotep_max */
17, /* packet type */
WIFI_FILTER_TO_LWIP, /* config_type */
WIFI_FILTER_MASK_LOCAL_PORT | WIFI_FILTER_MASK_PROTOCOL, /* match_mask */
},
/* DHCP */
{
0, /* remote ip */
67, /* local port */
0, /* localp_min */
0, /* localp_max */
0, /* remote_port */
0, /* remotep_min */
0, /* remotep_max */
17, /* packet type */
WIFI_FILTER_TO_LWIP, /* config_type */
WIFI_FILTER_MASK_LOCAL_PORT | WIFI_FILTER_MASK_PROTOCOL, /* match_mask */
}
}
TlsrChannel组件Repeater相关API如下:
-
telink_wifi_set_default_filter
-
telink_wifi_add_filter
-
telink_wifi_del_filter
-
telink_wifi_query_filter
API使用指南
| 名字 | 描述 |
|---|---|
| tlsr_channel_set_default_filter | 设置Repeater默认的过滤转发方向(预设为转发到host侧)。 |
| tlsr_channel_add_filter | 添加过滤规则到Repeater的转发表。 |
| tlsr_channel_del_filter | 从Repeater的转发表删除过滤规则。 |
| tlsr_channel_query_filter | 查询Repeater的转发表内容。 |
| tlsr_channel_reset_filter | 将filter配置重置,在bootup过程中Repeater会有一组预设filter配置,用户更新自定filter前,须先执行此API。 |
| tlsr_channel_send_to_host | Device传送消息到host,每次发送最大字节为1500 bytes。 |
| tlsr_channel_register_rx_cb | 注册接收处理回调函数,用于处理从host接收到的消息数据。 |
| tlsr_channel_host_ready | 此API用来判断host是否处于可以接收消息状态。 |
注意事项:
-
预设的Repeater组件过滤规则数为15组,如果想要修改,请调整CONFIG_SUPPORT_WIFI_REPEATER_IPV4_CNT宏。
-
在TLSR9118 bootup过程中,预设配置了多组filter。用户可以通过repeater filter show来查询:

建议用户在配置自定义filter之前执行tlsr_vlwip_netif_reset来进行清除动作。
芯片间心跳机制(TBD)
TlsrChannel组件使用指南
TlsrChannel组件作为TLSR9118系统的组成成份,本章节对如何将TlsrChannel软件包集成到TLSR9118软件包中做了详细描述,方便用户能够快速将TlsrChannel组件集成到TLSR9118版本中。
-
TlsrChannel软件包目录结构
-
TlsrChannel组件编译
TlsrChannel软件包目录结构(Need Confirm Later)
TlsrChannel软件包目录如图所示:

TlsrChannel组件编译
步骤1:TLSR9118侧编译
在TLSR9118下的wits-sdk目录执行:
make distclean make tlsr9118_sdio_defconfig
make tlsr9118_sdio_defconfig
make
在wits-sdk目录下生成wits.mcuboot.bin。
步骤2:Host侧编译
(1) cd xiaohu-ax/cp configs/cfg_xxx.mk cfg.mk
由于支持多种平台和配置模式,如SDIO OOB模式、SDIO INT模式、USB模式等。您需要依据平台与需求首先选择一个配置文件进行编译。系统默认提供配置文件保存在configs/目录下:
| 配置文件 | 平台/模式 |
|---|---|
| cfg_sdio_normal_fullhan.mk | fullhan, 4 bit mode interrupt |
| cfg_sdio_normal_goke.mk | goke, 4 bit mode interrupt |
| cfg_sdio_normal.mk | Linux PC, 4 bit mode interrupt |
| cfg_sdio_polling.mk | Linux PC, polling mode |
| cfg_sdio_oob_int_goke.mk | goke, OOB interrupt mode |
| cfg_usb.mk | Linux PC |
(2) 编译驱动sncmfmac.ko
虽然ARCH和CROSS_COMPILE在文件中已经配置好,但参数'KDIR'需要根据个人环境进行明确指定。
make KDIR=/home/apache/page/linux-4.9
如果没有指定'KDIR',系统则会使用默认的内核KDIR,这将编译出的驱动将适用于LinuxPC上运行。
make
(3) 编译应用程序
make KDIR=/home/apache/page/linux-4.9 apps
目前有两种类型的应用程序:
'sncm_cmd':通过主机命令进行Wi-Fi配置和控制。
'sncm_chn'(也称为TlsrChannel):Wi-Fi配置和控制在TLSR9118侧完成,通过TlsrChannel获取Wi-Fi连线信息。'sample_link'主要用于同步TLSR9118侧网络节点的Mac地址、IP地址等信息。
'sample_cli':主要用于发送客户自定义的信息。
TlsrChannel使用示例
配置网络信息并连接
首先在9118端,通过Wi-Fi软件开发指南中的章节Wi-Fi STA功能里的API函数或者CLI命令行来配置网络:

确认SDIO连接
在Host端,首先确认SDIO已连接:

建立TlsrChannel连接
(1) 9118端,初始化TlsrChannel。

(2) Host端通过sample_link自动获取wlan0 IP和mac地址。成功后,可以通过ifconfig wlan0确认信息已经同步。

更改转发规则
(1) Host ping通局域网里的PC1。

(2) 修改转发规则,只转发到9118端,不转发到Host端, Ping中断。

(3) 修改转发规则,同时转发到Host端和9118端,ping恢复。

mDNS开发指南
引言
mDNS开发指南旨在帮助实现需要运行mDNS的应用程序。
概述
TLSR9118 SDK使用lwIP's的mdns端口:
- API和CLI位于:
lib/net/mdns
TLSR9118的mDNS模块可以设置并充当mDNS响应器,也可以作为mDNS查询器启动服务查询作为可选功能。
构建
要使用mDNS API和CLI,用户应在构建配置中启用相应功能。
$ make tlsr9118s_defconfig
$ make menuconfig
选择 Kernel -> Networking support -> IPv4 support -> IP: mDNS responder support

选择 Command Line Interface -> Networking utilities -> mdns

退出并保存。
构建wits-mcuboot.bin。
$ make
请参考SDK入门指南下载镜像并在TLSR9118 EVK上运行。您将能够确认相关的CLI命令是否可用。

API
mDNS API提供以下一组函数来初始化和设置mDNS服务器并发送查询以搜索邻近设备上可用的服务。
-
mdns_resp_init
-
mdns_resp_add_netif
-
mdns_resp_remove_netif
-
mdns_resp_add_service
-
mdns_resp_del_service
-
mdns_search_service
-
mdns_search_stop
需要注意的是,这些函数必须由lwIP核心锁保护,因为有共享资源将由lwIP TCP/IP核心线程访问。参考mdns.c中的mdns() CLI入口函数。
初始化和设置mDNS响应器
- void mdns_resp_init(void)
初始化mDNS响应器。将在端口5353上打开UDP套接字。
相应的CLI命令:

- err_t mdns_resp_add_netif(struct netif netif, const char hostname)
为网络接口激活mDNS响应器。
| 参数 | 描述 |
|---|---|
| netif | 要激活的网络接口 |
| hostname | 要使用的名称。对于主机名的查询, .local将用该接口的IP地址回答。 |
返回值:如果netif被添加,则为ERR_OK,否则为err_t。
相应的CLI命令:

- err_t mdns_resp_remove_netif(struct netif *netif)
停止在此接口上响应mDNS查询,离开多播组,并释放帮助结构及其任何服务。
| 参数 | 描述 |
|---|---|
| netif | 要移除的网络接口 |
返回值:如果netif被移除,则为ERR_OK,否则为err_t。
相应的CLI命令:

管理mDNS响应器的服务
- s8_t mdns_resp_add_service(struct netif netif, const char name, const char service, enum mdns_sd_proto proto, u16_t port, service_get_txt_fn_t txt_f, void txt_data)
为选定的网络接口添加一个服务。
| 参数 | 描述 |
|---|---|
| nefit | 要在其上发布此服务的网络接口。 |
| name | 服务的名称。 |
| service | 服务类型,如"_http"。 |
| proto | 服务协议,对于TCP为DNSSD_PROTO_TCP("_tcp"),对于其他为DNSSD_PROTO_UDP("_udp")。 |
| port | 服务监听的端口。 |
| txt_fn | 获取TXT数据的回调。每次创建TXT回复时都会调用它,以允许动态回复。 |
| txt_data | txt_fn的用户数据指针。 |
返回值: 如果服务被添加到netif,则为Service_id,否则为err_t。
相应的CLI命令:

- err_t mdns_resp_del_service (struct netif *netif, u8_t slot)
在选定的网络接口上删除一个服务。
| 参数 | 描述 |
|---|---|
| netif | 服务应该被移除的网络接口。 |
| slot | mdns_resp_add_service 返回的服务槽号。 |
返回值:如果服务从netif中被移除,则为ERR_OK,否则为err_t。
相应的CLI命令:

开始服务查询
- err_t mdns_search_service (const char name, const char search, enum mdns_sd_proto proto, struct netif netif, search_result_fn_t result_fn, void arg, u8_t *request_id)
在网络上搜索特定服务。
| 参数 | 描述 |
|---|---|
| name | 服务的名称。 |
| service | 服务类型,如 "_http"。 |
| proto | 服务协议,对于TCP为DNSSD_PROTO_TCP("_tcp"),对于其他为DNSSD_PROTO_UDP("_udp")。 |
| netif | 发送搜索请求的网络接口。 |
| result_fn | 发送收到的答案的回调。对于匹配发送的请求的响应帧的每个答案,都会调用它。 |
| arg | result_fn 的用户数据指针。 |
| request_id | 返回的请求标识符,以允许停止它。 |
返回值:如果搜索请求被创建并发送,则为ERR_OK,否则为err_t。
相应的CLI命令:

这两个CLI命令都使用这个API,但参数不同。
停止服务查询
- void mdns_search_stop(u8_t request_id)
停止一个搜索请求。
| 参数 | 描述 |
|---|---|
| request_id | 要停止的搜索请求。 |
相应的CLI命令:

示例
目前没有专门的mDNS示例应用程序。相反,可以使用上面介绍的mDNS CLI命令来测试其功能。
连接到AP
Wi-Fi STA CLI命令可用于连接站点接口(例如 wlan0)到AP。
参考Wi-Fi软件开发指南以使用Wi-Fi站点CLI命令,因为Wi-Fi API函数和Wi-Fi CLI命令之间几乎总是一一对应的。

现在,mDNS响应器将回答同一网络上邻近设备的查询。为了查看这一点,可以使用来自与TLSR9118 EVB连接到同一局域网的Linux PC的 avahi-browse,如下所示。

由于已启动mDNS响应器,因此同一局域网上的任何对等设备(例如这台 Linux PC)都可以使用它的本地主机名,如下所示。

‘wits-wlan0’是通过CLI命令提供给mdns_resp_add_netif函数的主机名。它可以在用户应用程序中更改为任何合适的名称。
SNTP开发指南
引言
SNTP开发指南旨在帮助实现需要运行SNTP的应用程序。
概述
TLSR9118 SDK使用lwIP的SNTP端口:
- API和CLI位于:
lib/net/sntp
构建
要使用SNTP API和CLI,用户应在构建配置中启用相应功能。
$ make tlsr9118s_defconfig
$ make menuconfig
导航至Kernel -> Networking support -> IPv4 support -> IP: SNTP support并启用它。

同样,导航至Command Line Interface -> Networking utilities -> sntp并启用它。

退出并保存,然后构建wits-mcuboot.bin:
$ make
请参考SDK入门指南下载镜像并在TLSR9118 EVK上运行。您将能够确认相关的CLI命令是否可用。

API
SNTP API提供以下一组函数来设置SNTP模块并发送查询以获取网络时间。
-
sntp_setservername
-
sntp_init
-
sntp_set_time_sync_notification_cb
-
sntp_stop
设置和拆除
- void sntp_setservername(u8_t idx, const char *server)
通过名称初始化其中一个NTP服务器。
| 参数 | 描述 |
|---|---|
| Idx | 要设置的NTP服务器的索引,必须小于NTP_MAX_SERVERS。 |
| Server | 要设置的NTP服务器的DNS名称,将在联系时解析。 |
相应的CLI命令:

- void sntp_set_time_sync_notification_cb(void (*callback)(uint32_t sec, uint32_t us))
安装一个回调函数,当SNTP服务器的更新可用时将被执行。
| 参数 | 描述 |
|---|---|
| callback | 当网络时间可用时将被执行的回调函数。 |
相应的CLI命令:

- void sntp_stop(stop)
停止SNTP模块。
相应的CLI命令:

启动时间同步操作
- void sntp_init(void)
初始化此模块并立即发送请求或在SNTP_STARTUP_DELAY(_FUNC)之后发送请求。
相应的CLI命令:

示例
没有专门的SNTP示例应用程序。相反,可以使用上面介绍的SNTP CLI命令来测试其功能。
虽然SNTP API会处理SNTP协议本身,但应该有一种方法将其与系统时间同步。为此,用户应用程序可以使用 gettimeofday 和 settimeofday 函数,这也将在CLI示例中展示。
连接到AP
Wi-Fi STA CLI命令可以用于连接站点接口,即wlan0,到AP。参考Wi-Fi软件开发指南以使用Wi-Fi站点CLI命令。

按名称配置SNTP服务器
使用CLI命令sntp setserver [name]设置SNTP服务器。

启动SNTP服务
使用CLI命令sntp init启动SNTP服务。

现在,SNTP客户端请求将被发送到配置的服务器,该服务器将返回当前的网络时间。
CLI命令sntp init还注册了一个回调函数sntp_set_time_sync_notification_cb,展示了如何将系统本地时间与SNTP服务器返回的网络时间同步。

在此回调函数被调用之后,系统时间将与网络时间同步,这将通过调用settimeofday来检索。这由CLI命令sntp time如下所示演示:

HTTP服务器开发指南
HTTP服务器开发指南旨在帮助实现需要运行HTTP服务器的应用程序。
概述
TLSR9118 SDK使用ESP-IDF的esp_http_server模块:
- API 位于:
lib/net/esp_http_server - 示例位于:
api/examples/protocols/http_server
示例
要运行HTTP服务器示例,请按照以下步骤操作:
按如下设置构建配置
- 选择HTTP服务器示例作为主应用程序。
$ make tlsr9118_defconfig
$ make menuconfig
-
导航至 Applications -> Protocols Demo。
-
选择 Protocols Demo -> HTTP Server Demo。
-
退出并保存。
按如下设置Wi-Fi参数
$ make menuconfig
-
导航至 Applications -> Common -> include WI-FI Configuration。
-
输入 DEMO WI-FI Configuration 中的参数(如有需要,使用帮助菜单)。
-
退出并保存。
构建wits-mcuboot.bin.
$ make
- 参考SDK Getting Started Guide下载镜像并在TLSR9118 EVK上运行。

运行示例
本示例注册了3个URI,每个URI服务于不同的目的:
-
/hello: 用于响应客户端的GET HTTP方法的URI。
-
/echo: 用于响应客户端的POST HTTP方法的URI。
-
/ctrl: 用于响应客户端的PUT HTTP方法的URI。
本示例需要一个HTTP客户端,并且它应该与托管HTTP服务器的TLSR9118 EVB处于同一网络中,以便它们之间可以连接并交换HTTP消息。
运行HTTP客户端可能有许多不同的选择。在此示例中,使用了在与TLSR9118 EVB连接到同一网络的Linux PC上的curl命令。
测试URI
- /hello: 通过发送GET请求进行测试。


- /echo: 通过发送POST请求进行测试。


- /ctrl: 通过发送PUT请求进行测试。将"0"放入/ctrl将取消注册/hello和/echo URI,而将 "1" 放入/ctrl将注册这些URI。


HTTP客户端开发指南
概述
HTTP客户端开发指南旨在指导如何在TLSR9118平台上实现运行HTTP客户端的应用程序。
TLSR9118 SDK使用了来自ESP-IDF的 esp_http_client模块:
-
API路径:
lib/net/esp_http_client -
Demo路径:
api/examples/protocols/http_client
Demo配置与构建
要运行HTTP客户端Demo,请按照以下步骤进行操作。
设置构建配置
(1) 选择HTTP客户端Demo作为主应用程序:
$ make tlsr9xxxs_defconfig
$ make menuconfig
(2) 在menuconfig界面中,导航到以下选项:
-
Applications -> Protocols Demo -
选择:
Protocols Demo -> HTTP Client Demo -
Libraries/middleware -> net -> ESP HTTP Client -
选择:
Enable HTTP Basic Authentication -
选择:
Enable HTTP Digest Authentication
(3) 退出并保存配置。
设置测试HTTP服务器
(1) 在menuconfig界面中导航至Applications -> Example Configuration。
(2) 根据需要修改测试HTTP服务器的设置。

设置Wi-Fi参数
(1) 打开menuconfig界面:
$ make menuconfig
(2) 导航至Applications -> Common -> include WI-FI Configuration。
(3) 在DEMO WI-FI Configuration中输入Wi-Fi参数。如果需要,可以使用帮助菜单查看各项的详细说明。
(4) 退出并保存配置。
构建wits-mcuboot.bin
(1) 构建项目:
$ make
(2) 请参考SDK入门指南,将生成的镜像下载至TLSR9118 EVK,并运行。

使用HTTP客户端
HTTP客户端API提供了多种类型的HTTP请求功能,并支持身份认证、数据流传输和HTTPS连接。

基本HTTP请求
esp_http_client API支持执行GET、POST、PUT、PATCH和DELETE请求。一旦建立连接后,可以在关闭连接前发起多次请求。本节重点介绍使用URL或主机名测试REST API的常见用例。
- 使用URL进行测试

- 使用主机名进行测试

HTTP身份认证
HTTP客户端支持Basic和Digest两种身份认证方式。Digest身份认证支持MD5和SHA-256算法。
- Basic认证

- MD5认证

- SHA-256认证

HTTP数据流传输
- 对于需要主动控制数据交换的应用场景(如实时数据流传输),可以使用HTTP流模式。应用的执行流程与常规请求有所不同。

HTTP原生接口
HTTP客户端提供了底层API,可实现对HTTP连接的精细化控制。

HTTPS请求
HTTP客户端支持使用mbed TLS的SSL连接。推荐使用www.howsmyssl.com作为测试服务器进行演示。
获取根CA证书
对于HTTPS请求,需提供根CA证书(PEM文件)。以下示例演示如何使用openssl获取根CA证书:
openssl s_client -showcerts -connect www.howsmyssl.com:443 < /dev/null

HTTPS时间同步
为了进行HTTPS身份验证,设备时间必须同步。TLSR9118 SDK支持使用STNP进行时间同步。
- 如果时间未同步

- 时间同步完成

- 时间同步后,进行HTTPS请求

MQTT开发指南
MQTT开发指南旨在帮助实现需要MQTT客户端功能的应用程序。
概述
TLSR9118 SDK使用coreMQTT-Agent和底层coreMQTT:
-
API位于:
lib/mqtt/coreMQTT-Agent,lib/mqtt/coreMQTT -
示例位于:
api/examples/protocols/mqtt
示例操作指南
请按照以下步骤在TLSR9118平台上运行MQTT示例。
配置构建配置
(1) 选择MQTT示例作为主应用程序。
$ make tlsr9118s_defconfig
$ make menuconfig
(2) 进入以下路径:Applications -> Protocols Demo。
(3) 选择:Protocols Demo -> MQTT Demo。
(4) 退出并保存配置。
设置Wi-Fi参数
(1) 打开配置菜单:
$ make menuconfig
(2) 进入以下路径:Applications -> Common -> include WI-FI Configuration。
(3) 在以下路径中输入所需的Wi-Fi参数:DEMO WI-FI Configuration(如有需要,可以使用帮助菜单查看每个选项的说明)。
(4) 退出并保存配置。
配置MQTT客户端参数
(1) 再次打开配置菜单:
$ make menuconfig
(2) 进入以下路径:Applications -> MQTT demo
(3) 根据需要修改MQTT客户端参数。
(4) 构建固件映像文件:wits-mcuboot.bin。
$ make
(5) 参考SDK入门指南获取如何下载生成的wits-mcuboot.bin映像文件并将其烧录至TLSR9118开发板上的操作说明。

运行示例
要运行MQTT示例,需要一个独立的MQTT客户端,它将与同一个MQTT服务器test.mosquitto.org交互,可以作为发布者(Publisher)或订阅者(Subscriber)。本示例中使用了 Eclipse mosquito的PC版本作为测试客户端。
- 该示例允许使用CLI命令与MQTT客户端进行交互式测试。

初始化MQTT客户端
MQTT客户端可以通过mqtt init CLI命令进行初始化和启动。该命令需要以下几个参数:
| 参数 | 含义 | 是否必填 | 示例 |
|---|---|---|---|
| url | MQTT服务器的URL | M (必填) | test.mosquitto.org |
| port | MQTT服务器的端口号 | M (必填) | 1883 (明文传输); 8883 (加密传输,无认证); 8884 (加密传输,有认证) |
| secure | 0:明文TCP传输;1:TLS传输 | M (必填) | - |
| ca_file | CA 证书文件的完整路径 | secure为1时必填 | /path/to/ca.crt |
| client_cert_file | 客户端证书文件的完整路径 | secure为1时可选 | /path/to/client.crt |
| client_key_file | 客户端密钥文件的完整路径 | secure为1时可选 | /path/to/client.key |
使用明文TCP传输
以下是使用明文TCP传输初始化并启动MQTT客户端的CLI命令示例。该客户端将连接到已配置的MQTT服务器。
Wi-Fi参数应在构建过程中已配置完成。当mqtt init命令运行时,TLSR9118设备将自动连接到已配置的接入点(AP)。

使用TLS加密传输(仅加密)
要通过TLS连接,需要在文件系统中存储相应的CA证书。例如,TLSR9118 MQTT客户端可以使用CA证书(mosquitto.org.crt)连接到test.mosquitto.org的8883端口。

步骤:
(1) 从test.mosquitto.org下载CA证书到PC上。
(2) 使用fs load CLI命令将证书文件加载到目标设备的文件系统中。

(3) 文件传输可以使用YMODEM协议,具体取决于使用的终端程序。对于Tera Term:
- 进入
Transfer -> YMODEM -> Send,然后选择证书文件进行传输。

(4) 传输完成后,启用TLS初始化并启动MQTT客户端。

使用TLS加密传输和客户端认证
要进行客户端认证,需要准备客户端证书和客户端密钥。这些文件可以使用openssl工具生成,test.mosquito.org网站上有生成它们的详细指南。

步骤:
(1) 生成客户端证书和密钥。
(2) 使用fs load CLI命令将客户端证书和密钥文件加载到目标设备中(与CA证书的加载方式相同)。

(3) 启用TLS并进行客户端认证,初始化并启动MQTT客户端。

测试订阅特定主题
要测试订阅特定主题,请按以下步骤操作:
(1) 使用MQTT客户端的CLI命令订阅主题:

(2) 在PC上使用另一个MQTT客户端(如Eclipse Mosquitto)向相同的主题发布消息。

(3) 检查TLSR9118 MQTT客户端收到的消息。消息应显示在终端中。

测试向特定主题发布消息
要测试向特定主题发布消息,请按以下步骤操作:
(1) 在PC的MQTT客户端中订阅主题。

(2) 使用TLSR9118 MQTT客户端的CLI命令向相同主题发布消息。

(3) 检查PC客户端收到的消息,消息应显示TLSR9118客户端发布的内容。

CoAP开发指南
引言
CoAP开发指南提供了在 TLSR9118 平台上使用受限应用协议(CoAP)实现应用程序的详细说明。
概述
TLSR9118 SDK 集成了libcoap库,以促进基于 CoAP 的通信。libcoap 资源的组织如下:
-
API 位置:
lib/net/coap -
演示位置:
api/examples/protocols/coap
TLSR9118 中的 CoAP 模块可以作为 CoAP 服务器和/或 CoAP客户端运行。有关使用 libcoap API 的详细信息,请参阅 libcoap文档.
构建说明
要构建和运行支持 CLI 的 CoAP 演示,请按照以下步骤操作:
(1) 启用必要功能: 首先在构建配置中启用所需功能:
\$ make tlsr9xxxs_4m_defconfig
\$ make menuconfig
(2) 配置构建选项: 通过配置菜单导航以选择 CoAP 演示选项:
-
Applications -> Applications -> Protocols Demo -
Applications -> Protocols Demo -> CoAP Demo -
Applications -> CoAP Demo to configure additional options

(3) 保存配置: 退出配置菜单并保存更改。
(4) 构建固件: 构建wits-mcuboot.bin文件:
$ make
(5) 部署固件: 有关下载映像并在 TLSR9118 EVK 上运行的说明,请参阅SDK入门指南。
(6) 验证 CLI 命令: 部署后,确认相关 CLI 命令的可用性。
CoAP服务器程序演示
连接到接入点 (AP)
要将站点接口 (wlan0) 连接到接入点(AP),请使用 Wi-Fi 站点 (STA) 命令行界面 (CLI)命令。有关使用这些命令的全面说明,请参阅Wi-Fi软件开发指南。本指南提供了 Wi-Fi API 函数与其对应的CLI命令之间的详细映射。

注意
- 建议避免使用 wifi reg_evt_cb 命令,因为它会通过阻止接收 Wi-Fi 事件通知来干扰演示应用程序。
运行CoAP服务器程序
本节介绍如何使用命令行界面 (CLI) 命令启动和停止 CoAP 服务器程序。
启动CoAP服务器程序
要启动 CoAP 服务器程序,基本命令是:
coap_server start
但是,此命令使用默认配置启动CoAP服务器程序,并且不具备任何安全特性。要自定义服务器设置,coap_server start 命令支持多个选项:
coap_server start [-d max] [-g group] [-p port] [-A address] [-N] [[-k key] [-h hint]] [[-c certfile] [-m] [-C cafile]] [-E oscore_conf] [-j keyfile]
下面详细介绍了每个选项。
常规选项
-
-d max: 允许通过 PUT 方法创建动态资源,最多达到指定限制 (max)。如果达到限制,则会返回4.06错误代码,直到删除其中一个动态资源。 -
-g group: 在启动时加入指定的多播组。 -
-p port: 指定给定地址上用于侦听传入连接的端口。如果支持 (D)TLS,服务器程序还将在端口 + 1 上侦听 (D)TLS 连接。如果未指定,则默认端口为5683。 -
-A addr: 设置服务器程序将在其上侦听的接口的本地地址。 -
-N: 为"观测响应"发送非确认消息。如果未指定此选项,则将发送可确认的响应。即使设置了,根据RFC 7641的要求,每隔五个响应仍然是可确认的。
预共享密钥 (PSK) 选项
-
-h hint: 指定用于入站连接的预共享密钥标识。默认值为"CoAP"。如果已定义,则此字段不能为空。 -
-k key: 定义用于入站连接的预共享密钥。如果已定义,则此字段不能为空。注意:如果定义了-c cafile选项,则还必须定义-k key以使服务器程序能够同时支持 PSK 和 PKI。
公钥基础设施 (PKI) 选项
-
-c certfile: 使用指定的PEM文件,其中包含证书及私钥信息。注意:如果定义了 -k key,则还必须定义-C cafile 以使服务器程序能够同时支持 PSK 和 PKI 。 -
-m: 指示服务器程序缓冲证书文件。 -
-C cafile: 指定包含用于签署使用 -c certfile 定义的 certfile的 CA 证书的PEM 文件。如果已定义,则在 TLS 设置期间将此CA证书提供给客户端, 从而触发客户端证书验证。如果certfile是自签名的,则必须对certfile和cafile使用相同的文件名以触发验证(例如,-c certfile -C certfile)。 -
-j keyfile: 定义 PKI 使用的密钥文件。
OSCORE选项
-E oscore_conf: 指定OSCORE配置文件。
示例命令
以下示例启动一个支持PKI和OSCORE的CoAP服务器程序,使用指定的证书和密钥文件:
coap_server start -j /coap/certs/coap_server.key -C /coap/certs/coap_ca.pem -c /coap/certs/coap_server.crt -m -E /coap/oscore/coap_server_oscore.conf
注意
- 确保在启动服务器程序之前上传所有证书文件,参考上传证书文件。
停止CoAP服务器程序
要停止CoAP服务器程序并释放已分配的资源,请使用以下命令:
coap_server stop
启用mDNS响应器(可选)

启用 mDNS(多播DNS)响应器是一个可选步骤。若选择不启用此功能,则可以直接在后续步骤中使用设备的IP地址。
CoAP客户端演示程序
运行CoAP客户端
CoAP客户端演示程序支持各种命令行界面(CLI) 命令,包括以下内容:
-
GET/PUT/POST/DELETE: 执行GET、PUT、POST 或 DELETE 请求。
-
GET/PUT/POST/DELETE with Security: 执行启用了安全性的GET、PUT、POST 或 DELETE 请求。
-
Security Modes: 支持 PSK(预共享密钥)和PKI(公钥基础设施)安全性。
-
OSCORE: 受限 RESTful 环境的对象安全 (OSCORE)。
-
Subscription/Observation: 订阅或观察资源。
注意
- 使用 coap:// 方案将禁用安全性,而 coaps:// 方案将根据配置的设置启用安全性。

连接到AP: 按照章节运行coap服务器程序节中的说明连接到 AP。

注意
- 在运行CoAP客户端演示之前,请确保将 PKI 和 OSCORE 文件加载到系统中。
CoAP客户端命令
基本的GET请求
coap_client -m get coap://californium.eclipseprojects.io

使用 Non-Confirmable 消息进行广播操作。
coap_client -m get coap://255.255.255.255 -N

基于PSK安全性的GET请求
coap_client -m get -u password -k sesame coaps://californium.eclipseprojects.io

基于PKI安全性的GET请求
coap_client -m get -C /coap/certs/coap_ca.pem -c /coap/certs/coap_client.crt -j /coap/certs/coap_client.key coaps://californium.eclipseprojects.io

GET/PUT/DELETE操作
coap_client -m get coap://[192.168.3.18]/Telink
coap_client -m put coap://[192.168.3.18]/Telink -e "ABC"
coap_client -m get coap://[192.168.3.18]/Telink
coap_client -m delete coap://[192.168.3.18]/Telink

注意
- 请确保对端 CoAP Server 已注册正确的 URI。如果使用 TLSR9118 作为CoAP Server,在开启 CoAP Server 时请使用
-d max选项,具体请参考章节常规选项。本例中,对端CoAP Server使用命令coap_server start -d 1启动。
订阅/观察资源
-s duration: 订阅/观察资源指定时长(单位:秒):
coap_client -s 60 -m get coap://\[192.168.3.18\]/time

使用OSCORE安全性运行
要使用OSCORE安全性,请确保在配置菜单中启用了"将OSCORE用作CoAP安全机制"。

基于OSCORE安全性的GET请求
coap_client -m get -E /coap/oscore/coap_client_oscore.conf coap://californium.eclipseprojects.io

基于PKI + OSCORE安全性的GET请求
coap_client -m get -E /coap/oscore/coap_client_oscore.conf -C /coap/certs/coap_ca.pem -c /coap/certs/coap_client.crt -j /coap/certs/coap_client.key coaps://californium.eclipseprojects.io

基于PSK + OSCORE安全性的GET请求
coap_client -m get -E /coap/oscore/coap_client_oscore.conf -u password -k sesame coaps://californium.eclipseprojects.io

上传证书文件
启用TLSR_FS CLI工具
要为文件上传配置TLSR_FS CLI工具:
访问配置菜单:
-
导航到配置菜单;
-
选择SDK;
-
选择包含TLSR_FS CLI工具。


TLSR_FS CLI工具支持多个命令,包括:
-
fs load: 通过 YMODEM 从本地计算机上传文件。 -
fs read: 读取文件的内容。 -
fs write: 将数据写入文件。 -
fs rm: 删除文件。 -
fs size: 查询文件的大小。

上传证书文件
对于 CoAP 演示,fs load命令用于上传证书文件。按照以下步骤将文件上传到WITS用于演示:
上传文件
使用fs load命令并指定上传文件保存的文件名。

选择文件
从 YMODEM 选择要上传的文件。


读取上传的文件
使用fs read命令及文件名读取指定文件的内容。

查看目录中的文件
使用ls命令查看当前目录下的文件列表。

注意
ls命令必须在配置菜单中启用,并如上所述包含在构建中。

AT指令
简介
本章节提供关于如何使用TLSR9118 AT指令的全面指南。
AT指令类型
AT指令用于控制和与TLSR9118交互。它们分为四种类型:
| 类型 | 指令格式 | 描述 |
|---|---|---|
| 测试指令 | AT+WL[接口]+<指令名称>=? | 查询设置指令的内部参数及其允许的取值范围。[接口] 可以是0或1,代表不同的接口。 |
| 查询指令 | AT+ WL[接口]+<指令名称>? | 返回指定参数的当前值。 |
| 设置指令 | AT+ WL[接口]+<指令名称>=<值> 设 | 置指令中用户定义的参数的值,并随后执行这些指令。值可以是字符串或整数。 |
| 执行指令 | AT+ WL[接口]+<指令名称> | 执行不需要任何用户定义参数的指令。 |
注意
- 并非所有AT指令都支持以上列出的所有四种类型。
- <指令名称>代表特定的AT指令,例如CMUX、CFUN等。
- 字符串参数应使用双引号括起来。例如:AT+CMUX="Hello"。
- 整数参数应在相应的测试指令指定的允许范围内。
- 尖括号“< >”表示不能省略的必需参数。
示例:
-
测试指令:AT+WL0+CMUX=?
-
查询指令:AT+WL1+CFUN?
-
设置指令:AT+WL0+CMUX="1,1"
-
执行指令:AT+WL1+CFUN=1
基本AT指令
概述
TLSR9118无线Wi-Fi模组可以通过串口使用标准AT指令进行控制。本节提供基本AT指令列表,用于实现基本功能。
| 指令 | 描述 |
|---|---|
| AT | 测试AT启动 |
| AT+RST | 重启模组 |
| AT+GSLP | 进入睡眠模式 |
| ATE | 启用/禁用AT指令回显 |
| AT+UART_CUR | 配置UART设置 |
| AT+SLEEP | 设置睡眠模式 |
| AT+SLEEPWKCFG | 配置唤醒源和GPIO |
指令
AT – 测试AT启动
类型:执行;
描述:该指令测试Wi-Fi模组的基本通信和设置;
指令:AT;
预期响应:OK;
参数:无。
AT+RST – 重启模组
类型:执行;
描述:该指令重启Wi-Fi模组;
指令:AT+RST;
预期响应:OK;
参数:无。
AT+GSLP – 进入深度睡眠模式
类型:设置;
描述:该指令使模组进入深度睡眠模式,持续指定的时间;
指令:AT+GSLP=<time>
预期响应:<time> OK。
| 参数 | 描述 |
|---|---|
| <time> | 睡眠持续时间,单位为毫秒。值为0表示无限期睡眠,直到被外部源唤醒。 注意:由于其他唤醒源(如系统定时器)的影响,模组可能会比指定的<time>更早唤醒。 |
ATE – AT指令回显
类型:设置;
描述:该指令启用或禁用AT指令的回显;
指令:ATE<value>;
预期响应:OK;
参数:
<value>:
-
0: 禁用回显;
-
1: 启用回显。
AT+UART_CUR – 当前UART通信设置
类型:设置;
描述:该指令配置当前UART通信设置。请注意,这些设置不会持久保存,也不会覆盖存储在闪存中的默认波特率;
指令:AT+UART_CUR=<baudrate>, <databits>, <stopbits>, <stopbits>, <flow control>;
示例:AT+UART_CUR=115200, 8, 1, 0, 3;
预期响应:OK。
| 参数 | 描述 | 可能的值 |
|---|---|---|
| <baudrate> | 波特率 | 最高115200 |
| <databits> | 数据位 | 5、6、7或8 |
| <stopbits> | 停止位 | 1 (1位)、2 (1.5位)、3 (2位) |
| <stopbits> | 奇偶校验 | 0 (无)、1 (奇校验)、2 (偶校验) |
| <flow control> | 流控制 | 0 (禁用)、1 (RTS)、2 (CTS)、3 (RTS和CTS) |
注意
- 这些设置不会保存到闪存,模组重启后将丢失。
- 流控制功能需要硬件支持。
AT+SLEEP – 睡眠模式
类型:设置/查询;
描述:该指令控制模组的睡眠模式。它仅在模组处于工作站(STA)模式时适用;
查询指令:AT+SLEEP?;
查询响应:+SLEEP: <sleep mode> OK;
设置指令: AT+SLEEP=<sleep mode>;
设置响应:OK;
参数:
<sleep mode>:
-
0: 禁用睡眠模式;
-
1: 轻度睡眠模式;
-
2: 深度睡眠模式;
-
3: 休眠模式。
AT+SLEEPWKCFG – 配置唤醒源
类型:设置;
描述:该指令配置唤醒源和负责从睡眠模式唤醒模组的GPIO引脚;
指令:AT+SLEEPWKCFG=<wakeup source>,<param1>,[<param2>];
示例:AT+SLEEPWKCFG=2,6;
预期响应:OK。
| 参数 | 描述 |
|---|---|
| <wakeup source> | 0: 保留(不支持); 1: 保留(不支持)。 2: GPIO。 |
| <param1> | GPIO 引脚编号(如果 <wakeup source> 设置为 2,表示 GPIO)。 |
| <param2> (可选) | GPIO 引脚的唤醒电平(如果 <wakeup source> 设置为 2,表示 GPIO)。 0: 低电平触发唤醒; 1: 高电平触发唤醒。 |
Wi-Fi相关AT指令
概述
本节介绍用于控制和配置TLSR9118 Wi-Fi功能的AT指令。这些指令以AT+WL0或AT+WL1开头,其中WL0和WL1指的是模组上的两个可用Wi-Fi接口。您应在指令中将WL0/1替换为相应的接口标识。
指令
AT+CWINIT – 初始化或取消初始化Wi-Fi驱动程序
类型:设置;
描述:此指令初始化或取消初始化指定接口的Wi-Fi驱动程序;
指令:AT+WL0/1+CWINIT=<enable>;
预期响应:OK。
参数:
<enable>:启用或禁用Wi-Fi驱动程序。
-
1: 初始化驱动程序;
-
0: 取消初始化驱动程序。
AT+CWMAC – Wi-Fi MAC地址
类型:设置/查询;
描述:此指令设置或获取指定Wi-Fi接口的MAC地址;
查询指令:AT+ WL0/1+CWMAC?;
查询响应:+CWMAC: <MAC address> OK;
设置指令:AT+ WL0/1+CWMAC=<MAC address>;
设置响应:OK。
| 参数 | 描述 | 示例 |
|---|---|---|
| <MAC address> | 要设置的MAC地址(格式为 XX:XX:XX:XX:XX:XX) | 64:f9:47:f0:03:38 |
AT+CWMODE – Wi-Fi工作模式
类型:设置/查询;
描述:此指令设置或获取指定Wi-Fi接口的工作模式;
测试指令:AT+ WL0/1+CWMODE=?;
测试响应:+CWMODE: <mode> OK;
查询指令:AT+ WL0/1+CWMODE?;
查询响应:+CWMODE:<mode> OK;
设置指令:AT+ WL0/1+CWMODE=<mode>;
设置响应:OK。
参数:
<mode>:Wi-Fi工作模式。
-
1: 工作站模式 (STA);
-
2: 软接入点模式 (SoftAP)。
AT+CWJAP – 接入点(AP)的配置
类型:设置/查询;
描述:此指令设置或获取工作站模式下连接到接入点(AP)的配置;
示例(设置):AT+WL0+CWJAP=XH-Test 00000000 0 0 0;
查询指令:AT+ WL0+CWJAP?;
查询响应:+CWJAP:<ssid>,<bssid>,<channel>,<rssi>,<mode: 11n:0/1 11ax:0/1> OK;
设置指令:AT+ WL0/1+CWJAP=<ssid> <pwd> <alg> <proto> <pmf>;
设置响应:OK或ERROR。
| 参数 | 描述 | 可能的值/说明 |
|---|---|---|
| <ssid> | 目标AP的SSID(字符串,用双引号括起来) | - |
| <pwd> | AP的密码(字符串,用双引号括起来,最多63个ASCII字符) | - |
| <alg> | 成对密码类型 | 0: OPEN; 1: WEP; 2: TKIP; 3: CCMP; 6: SAE; 7: CCMP256 |
| <proto> | 加密协议 | 0: OPEN; 1: WPA_PSK; 2: WPA2_PSK |
| <pmf> | 受保护管理帧 | 0: 禁用PMF; 1: 支持PMF(首选); 3: 要求PMF |
注意
- PMF(受保护管理帧)通过防止伪造和重放攻击来增强管理帧的安全性。
AT+CWCAP – 与AP连接
类型:执行;
描述:此指令根据之前使用AT+CWJAP设置的配置启动与AP的连接;
指令:AT+WL0+CWCAP;
预期响应:OK。
AT+CWDHCP – 启用或禁用DHCP
类型:设置/查询;
描述:此指令启用或禁用指定Wi-Fi接口上的DHCP客户端。启用后,设备将自动从DHCP服务器获取IP地址。
查询指令:AT+ WL0/1+CWDHCP?;
查询响应:+CWDHCP:<state> OK;
设置指令:AT+ WL0/1+ CWDHCP =<operate>;
设置响应:OK;
参数:
<operate>:
-
0: 禁用 DHCP;
-
1: 启用DHCP。
AT+CWSTR – 启动Wi-Fi
类型:执行;
描述:此指令启动指定接口上的Wi-Fi功能;
指令:AT+ WL0/1+ CWSTR;
预期响应:OK。
AT+CWSTOP – 停止Wi-Fi
类型:执行;
描述:此指令停止指定接口上的Wi-Fi功能;
指令:AT+ WL0/1+ CWSTOP;
预期响应:OK。
AT+CWQAP – 断开与当前AP的连接
类型:执行;
描述:此指令断开工作站与当前连接的AP的连接;
指令:AT+ WL0/1+ CWQAP;
预期响应:OK
AT+CWPWR – 查询或设置最大发射功率限制
类型:设置/查询;
描述:此指令查询或设置指定Wi-Fi接口的最大发射功率限制;
查询指令:AT+ WL0/1+ CWPWR?;
查询响应:+CWPWR: max limited power = <power_value> OK;
设置指令:AT+ WL0/1+ CWPWR=<pwr>;
设置响应:OK;
参数:<pwr>: 最大发射功率限制。
AT+CWPS – 查询或设置Wi-Fi省电模式
类型:设置/查询;
描述:此指令查询或设置指定Wi-Fi接口的省电模式。当设备空闲时,省电模式可以帮助降低功耗。
查询指令:AT+ WL0/1+ CWPS?;
查询响应:+CWPS:ps type = <WIFI_PS_NONE, WIFI_PS_MIN_MODEM, WIFI_PS_MAX_MODEM> OK;
设置指令:AT+WL0/1+ CWPS=<mode>;
设置响应:OK。
参数:
<mode>:省电模式。
-
0: WIFI_PS_NONE(不省电);
-
1: WIFI_PS_MIN_MODEM(最小省电);
-
2: WIFI_PS_MAX_MODEM(最大省电)。
AT+CWLAPOPT – 配置AT+CWLAP指令的行为
类型:设置;
描述:此指令配置AT+CWLAP指令的行为,该指令用于扫描可用的接入点 (AP)。它允许您控制扫描结果的排序方式以及显示哪些参数。
示例:AT+CWLAPOPT=1 511;
指令:AT+WL0/1+CWLAPOPT =<sort_enable>, <mask>;
预期响应:OK或ERROR。
| 参数 | 描述 | 可能的值/说明 |
|---|---|---|
| <sort_enable> | 启用或禁用按RSSI对扫描结果进行排序 | 0:不按RSSI排序; 1:按RSSI排序(降序,信号强度最高的排在最前面) |
| <mask> | 一个位掩码,用于控制AT+CWLAP结果中显示哪些参数 | 每一位对应一个参数(参见下表)。将位设置为1以启用该参数的显示,0为禁用。 |
<mask>位掩码:
| 位 | 参数 | 描述 |
|---|---|---|
| 0 | <encrypt> | 加密状态(例如,开放、WPA2) |
| 1 | <ssid> | AP的SSID |
| 2 | <rssi> | 接收信号强度指示器(RSSI) |
| 3 | <bssid> | AP的MAC地址 |
| 4 | <ch> | AP的信道 |
| 5 | <pairwits_cipher> | AP使用的成对密码 |
| 6 | <group_cipher> | AP使用的组密码 |
| 7 | <NGB> | 是否支持802.11b/g/n(支持则为1,否则为0) |
| 8 | <WPS support> | 是否支持Wi-Fi保护设置(WPS) |
AT+CWLAPN – 查询可用接入点(AP)的数量
类型:查询;
描述:此指令查询上次Wi-Fi扫描期间找到的可用接入点(AP)的数量。
指令:AT+WL0/1+ CWLAPN?;
预期响应:+CWLAPN:ap_num=<number_of_aps> OK。
AT+CWSSCN – 停止Wi-Fi扫描
类型:执行;
描述:此指令停止正在进行的Wi-Fi扫描;
指令:AT+WL0/1+CWSSCN;
预期响应:OK。
AT+CWLAP – 显示可用接入点(AP)
类型:执行/查询;
描述:此指令启动对可用接入点(AP)的扫描,并可选择显示结果;
示例:
-
AT+WL0+CWLAP: 扫描接口WL0上所有可用的AP。
-
AT+CWLAP?: 显示接口 WL1 上上次扫描的结果。
-
AT+CWLAP=ssid=MyNetwork ch=6: 扫描信道6上SSID为"MyNetwork"的AP。
查询指令(显示上次扫描结果):AT+WL0/1+CWLAP?;
查询响应:+CWLAP:ap[i]=<ssid> <authmode> = b:0/1 g:0/1 n:0/1 ax:0/1 OK;
执行指令(启动扫描):AT+WL0/1+CWLAP;
执行响应:OK;
执行指令(过滤扫描):AT+WL0/1+CWLAP=<ssid=> <bssid=> <ch=> <scantype=> <actmin=> <actmax=> <num=>;
执行响应:OK。
| 参数 | 描述 | 可能的值/说明 |
|---|---|---|
| <ssid> | 要搜索的AP的SSID(字符串,用双引号括起来) | - |
| <bssid> | 要搜索的 AP 的 MAC 地址 | - |
| <ch> | 要扫描的信道 | - |
| <scantype> | 要执行的扫描类型 | `0`:主动扫描; `1`:被动扫描。 |
| <actmin> | 每个信道的最小主动扫描时间(毫秒,范围:0-1500) | 仅对主动扫描有效。 |
| <actmax> | 每个信道的最大主动扫描时间(毫秒,范围:0-1500) | - |
| <num> | 结果中要显示的最大 AP 数量 | - |
AT+ CWLIF– 获取IP和MAC
类型:执行;
描述:此指令检索当前连接到指定接口上的SoftAP的工作站的IP地址和MAC地址;
指令:AT+CWLIF;
预期响应:+CWLIF: <ip_address>, <mac_address> OK。
AT+ CWDHCPS– 查询或配置IPv4地址范围
类型:设置/查询;
描述:此指令查询或配置SoftAP上的DHCP服务器将分配给已连接客户端的IPv4地址范围。
查询指令:AT+WL0/1+CWDHCPS?;
查询响应:+CWDHCPS: <lease_time>,<start_ip>,<end_ip> OK;
设置指令:AT+WL0/1+CWDHCPS=<enable>,<lease_time>,<start_ip>,<end_ip>;
设置响应:OK。
| 参数 | 描述 | 说明 |
|---|---|---|
| <enable> | 启用或禁用DHCP服务器 | 1: 启用DHCP服务器并配置地址范围; 0: 禁用DHCP服务器并使用默认地址范围。 |
| <lease_time> | DHCP租约时间(分钟) | 范围:1-2880 |
| <start_ip> | DHCP范围的起始IPv4地址 | - |
| <end_ip> | DHCP范围的结束IPv4地址 | - |
AT+ CWSAP– 查询或配置SoftAP设置
类型:设置/查询;
描述:此指令查询或配置指定接口上的SoftAP的设置;
查询指令:AT+WL0/1+CWSAP?;
查询响应:+CWSAP: <ssid>,<pwd>,<channel>,<ecn>,<proto>,<max_conn>,<ssid_hidden> OK;
设置指令:AT+WL0/1+CWSAP=<ssid>,<pwd>,<chl>,<ecn>,<proto>,[<max_conn>],[<ssid_hidden>];
设置响应:OK。
| 参数 | 描述 | 可能的值/说明 |
|---|---|---|
| <ssid> | SoftAP的SSID(字符串,用双引号括起来) | - |
| <pwd> | SoftAP的密码(字符串,用双引号括起来,8-63个ASCII字符) | - |
| <chl> | SoftAP的信道 | - |
| <ecn> | 加密方法 | 0: OPEN; 3: CCMP(不支持WEP和TKIP) |
| <proto> | 安全协议 | 0: NONE; 2: WPA2(仅支持WPA2) |
| [<max_conn>] | 可以连接到SoftAP的最大工作站数量 | 可选参数 |
| [<ssid_hidden>] | 控制是否广播SSID | 0: 广播 SSID(默认); 1: 隐藏 SSID。 |
AT+ CWCOUNTRY– 查询或设置Wi-Fi国家/地区代码
类型:设置/查询;
描述:此指令查询或设置指定接口的Wi-Fi国家/地区代码。国家/地区代码会影响Wi-Fi操作的可用信道和监管域。
查询指令:AT+WL0/1+CWCOUNTRY?;
查询响应:Country: <country_code>,<total_channel_count> OK;
设置指令:AT+WL0/1+CWCOUNTRY=<country_code>;
设置响应:OK。
| 参数 | 描述 | 说明 |
|---|---|---|
| <country_code> | 两字母ISO 3166-1 alpha-2国家/地区代码(例如,美国的"US") | - |
| <total_channel_count> | 指定国家/地区可用的Wi-Fi信道总数 | 此值由查询指令返回。 |
示例
本节提供一些实际示例,说明如何使用Wi-Fi相关AT指令执行常见任务。
连接到AP并通过DHCP获取IP地址
此示例演示如何连接到接入点(AP),然后使用DHCP自动获取IP地址。
AT+WL0+CWINIT=1//初始化接口WL0上的Wi-Fi驱动程序
OK
AT+WL0+CWMODE=1//将Wi-Fi模式设置为工作站(STA)
OK
AT+WL0+CWJAP=HUAWEI-Test 00000000 0 0 0//连接到AP "HUAWEI-Test"(开放网络)
OK
AT+WL0+CWCAP//启动连接
OK
WIFI CONNECTED //(非请求响应,指示连接成功)
AT+WL0+CWDHCP=1 //启用DHCP客户端
OK
WIFI GOT IP //(非请求响应,指示已获取IP地址)
扫描可用AP并显示结果
此示例演示如何扫描可用AP,然后显示扫描结果。
AT+WL0+CWINIT=1 // 初始化Wi-Fi驱动程序
OK
AT+WL0+CWMODE=1 //将Wi-Fi模式设置为工作站(STA)
OK
AT+WL0+CWLAP //启动Wi-Fi扫描
OK
AT+WL0+CWLAP? //显示扫描结果
+CWLAP:ap[0] = SC-Ent authmode = 7 b:1 g:1 n:1 ax:0
+CWLAP:ap[1] = SC-IoT authmode = 6 b:1 g:1 n:1 ax:0
+CWLAP:ap[2] = SC-Guest authmode = 6 b:1 g:1 n:1 ax:0
+CWLAP:ap[3] = NFC authmode = 6 b:1 g:1 n:1 ax:1
+CWLAP:ap[4] = apache_test authmode = 3 b:1 g:1 n:1 ax:1
+CWLAP:ap[5] = CMCC-AP2 authmode = 3 b:1 g:1 n:1 ax:1
+CWLAP:ap[6] = RT-BE88U-MLO authmode = 8 b:1 g:1 n:1 ax:1
+CWLAP:ap[7] = CMCC-CQpy authmode = 3 b:1 g:1 n:1 ax:1
+CWLAP:ap[8] = CMCC-CQpy-3 authmode = 3 b:1 g:1 n:1 ax:1
+CWLAP:ap[9] = SC-Ent authmode = 7 b:1 g:1 n:1 ax:0
+CWLAP:ap[10] = SC-Guest authmode = 6 b:1 g:1 n:1 ax:0
+CWLAP:ap[11] = SC-IoT authmode = 6 b:1 g:1 n:1 ax:0
+CWLAP:ap[12] = SC-IoT authmode = 6 b:1 g:1 n:1 ax:0
+CWLAP:ap[13] = DIRECT-E3MROOM4msUN authmode = 3 b:0 g:1 n:1 ax:1
+CWLAP:ap[14] = SC-Ent authmode = 7 b:1 g:1 n:1 ax:0
+CWLAP:ap[15] = SC-IoT authmode = 6 b:1 g:1 n:1 ax:0
+CWLAP:ap[16] = wULu63h authmode = 3 b:1 g:1 n:1 ax:0
+CWLAP:ap[17] = TP-LINK_3A1D_2G authmode = 0 b:1 g:1 n:1 ax:1
+CWLAP:ap[18] = HUAWEI-Test authmode = 0 b:1 g:1 n:1 ax:1
+CWLAP:ap[19] = Xiaomi_7AB6 authmode = 0 b:1 g:1 n:1 ax:1
+CWLAP:ap[20] = SWaJrIdwhRkTT4e1nu authmode = 0 b:1 g:1 n:1 ax:1
+CWLAP:ap[21] = letter_sap authmode = 0 b:1 g:1 n:1 ax:1
+CWLAP:ap[22] = xiaohu_test authmode = 0 b:1 g:1 n:1 ax:0
+CWLAP:ap[23] = 3333h authmode = 0 b:1 g:1 n:1 ax:0 OK
注意
- +CWLAP响应的格式可能会因AT+CWLAPOPT设置的配置而异。
启动SoftAP并配置DHCP服务器
此示例演示如何配置和启动SoftAP,然后设置DHCP服务器以将IP地址分配给连接的客户端。
AT+CWINIT=1 // 初始化Wi-Fi驱动程序
OK
AT+WL1+CWMODE=2 // 在接口WL1上将Wi-Fi模式设置为SoftAP
OK
AT+WL1+CWDHCPS=1,1,"192.168.66.2","192.168.66.10" // 启用DHCP服务器并配置IP地址范围
OK
AT+WL1+CWSAP="telink_test",12345678,6,3,2 // 配置SoftAP设置(SSID、密码等)
OK
AT+WL1+CWDHCPS? // 查询DHCP服务器配置(可选)
+CWDHCPS:1,"192.168.66.2","192.168.66.10"
TCP/IP AT指令
概述
本节介绍用于控制和配置TLSR9118模组TCP/IP网络功能的AT指令。这些指令允许您建立TCP和UDP连接、发送和接收数据、管理网络接口以及执行网络诊断。
指令
AT+CIFSR - 获取本地IP地址和MAC地址
类型:执行;
描述:此指令检索工作站(STA)和软AP(AP)接口的本地IP地址(IPv4和IPv6)以及MAC地址;
指令:AT+CIFSR;
预期响应:
-
+CIFSR:STAIP,"<STA_IPv4_address>"
-
+CIFSR:STAMAC,"<STA_MAC_address>"
-
+CIFSR:STAIP6LL,"<STA_IPv6_link-local_address>"
-
+CIFSR:STAIP6GL,"<STA_IPv6_global_address>"
-
+CIFSR:APIP,"<AP_IPv4_address>" OK。
注意
- 模组配置中必须启用IPv6支持(例如,通过启用LWIP_IPV6 kconfig选项)才能显示IPv6地址。
AT+CIPSTA - 查询/设置工作站的IP地址
类型:设置/查询;
描述:此指令查询或设置工作站(STA)接口的IPv4地址、网关和子网掩码,它还可以查询IPv6链路本地地址和全局地址;
查询指令:AT+CIPSTA?;
查询响应:
-
+CIPSTA:ip,"<STA_IPv4_address>"
-
+CIPSTA:gateway,"<gateway_address>"
-
+CIPSTA:netmask,"<netmask>"
-
+CIPSTA:ip6ll,"<STA_IPv6_link-local_address>"
-
+CIPSTA:ip6gl,"<STA_IPv6_global_address>" OK。
设置指令:AT+CIPSTA="<IPv4_address>",[,"<gateway_address>", "<netmask>"];
设置响应:OK。
| 参数 | 描述 |
|---|---|
| <IPv4_address> | 要分配给 STA 接口的 IPv4 地址(字符串,用双引号括起来) |
| <gateway_address> | 网关地址(可选,字符串,用双引号括起来) |
| <netmask> | 子网掩码(可选,字符串,用双引号括起来) |
注意
- 此指令不能设置IPv6地址。
AT+CIPSTAMAC - 查询/设置工作站的MAC地址
类型:设置/查询;
描述:此指令查询或设置工作站(STA)接口的MAC地址;
查询指令:AT+CIPSTAMAC?;
查询响应:+CIPSTAMAC: "<MAC_address>" OK;
设置指令:AT+CIPSTAMAC=<MAC_address>;
设置响应:OK。
| 参数 | 描述 | 示例 |
|---|---|---|
| <MAC_address> | 要分配给STA接口的MAC地址(字符串,用双引号括起来) | 64:f9:47:f0:03:38 |
AT+CIPAP - 查询/设置软AP的IP地址
类型:设置/查询;
描述:此指令查询或设置软AP (AP)接口的IPv4地址、网关和子网掩码;
查询指令:AT+CIPAP?;
查询响应:
-
+CIPAP:ip,"<AP_IPv4_address>"
-
+CIPAP:gateway,"<gateway_address>"
-
+CIPAP:netmask,"<netmask>" OK;
设置指令:AT+CIPAP="<IPv4_address>",[,"<gateway_address>","<netmask>"];
设置响应:OK。
| 参数 | 描述 |
|---|---|
| <IPv4_address> | 要分配给 AP 接口的 IPv4 地址(字符串,用双引号括起来) |
| <gateway_address> | 网关地址(可选,字符串,用双引号括起来) |
| <netmask> | 子网掩码(可选,字符串,用双引号括起来) |
AT+CIPAPMAC - 查询/设置软AP的MAC地址
类型:设置/查询;
描述:此指令查询或设置软AP (AP)接口的MAC地址;
查询指令:AT+CIPAPMAC?;
查询响应:+CIPAPMAC: "<MAC_address>" OK;
设置指令:AT+CIPAPMAC=<MAC_address>;
设置响应:OK。
| 参数 | 描述 | 示例 |
|---|---|---|
| <MAC_address> | 要分配给AP接口的MAC地址(字符串,用双引号括起来) | 64:f9:47:f0:03:39 |
AT+PING - Ping远程主机
类型:执行;
描述:此指令向远程主机发送ICMP回显请求(ping),以测试网络连接;
指令:AT+PING=<host>;
示例:
-
AT+PING="192.168.1.1"
-
AT+PING="www.baidu.com"
预期响应(成功):+PING: <time_in_milliseconds> OK;
预期响应(超时):+PING:TIMEOUT ERROR
| 参数 | 描述 |
|---|---|
| <host> | 远程主机的IP地址(IPv4或IPv6)或域名(字符串,用双引号括起来) |
注意
- 模组配置中必须启用IPv6支持才能ping IPv6地址。
AT+CIPSTART - 建立TCP连接、UDP传输或SSL连接
类型:执行;
描述:此指令与远程主机建立TCP、UDP或SSL连接;
TCP/SSL连接指令:AT+CIPSTART=<link_ID>,<type>,<remote_host>,<remote_port>[,<keep_alive>];
TCP/SSL连接示例:
-
AT+CIPSTART=1, TCP, 192.168.3.98, 9001
-
AT+CIPSTART=4, SSL, 192.168.3.98, 9002
TCP/SSL连接预期响应:Link id: <link_ID>, CONNECT OK;
UDP连接指令:AT+CIPSTART=<link_ID>,<type>,<remote_host>,<remote_port>,<local_port>;
UDP连接示例:AT+CIPSTART=3,"UDP","192.168.3.98",8080,1113;
UDP连接预期响应:
-
Local port: <local_port>
-
Link id: <link_ID>, CONNECT OK。
| 参数 | 描述 | 可能的值/说明 |
|---|---|---|
| <link_ID> | 表示连接ID的整数(0-4) | - |
| <type> | 连接类型 | "TCP"、"UDP"或"SSL" |
| <remote_host> | 远程主机的IP地址(IPv4或IPv6)或域名(字符串,用双引号括起来) | 最大长度:64 字节 |
| <remote_port> | 远程主机的端口号 | - |
| <keep_alive> (仅限TCP/SSL) | 启用或禁用TCP保持连接 | 0: 禁用保持连接(默认)。 1-7200: 启用保持连接,指定的时间间隔(以秒为单位)。 |
| <local_port> (仅限UDP) | 要使用的本地UDP端口 | - |
注意
- 模组配置中必须启用IPv6支持才能连接到IPv6地址。
AT+CIPSTATUS - 获取TCP/UDP/SSL连接状态和信息
类型:执行;
描述:此指令检索有关活动TCP、UDP和SSL连接的状态和信息;
指令:AT+CIPSTATUS;
预期响应:+CIPSTATUS: <link_ID>,<type>,<remote_host>,<remote_port>,<local_port>,<status> OK。
| 参数 | 描述 | 可能的值 |
|---|---|---|
| <link_ID> | 连接ID | - |
| <type> | 连接类型 | "TCP"、"UDP"或"SSL" |
| <remote_host> | 远程主机的IP地址或域名 | - |
| <remote_port> | 远程端口号 | - |
| <local_port> | 本地端口号(对于UDP连接) | - |
| <server> | 连接状态 | - |
AT+CIPSEND – 在普通传输模式或Wi-Fi透传模式下发送数据
类型:执行;
描述:此指令通过已建立的TCP、UDP或SSL连接发送数据,它支持两种模式:普通传输模式和Wi-Fi透传模式;
普通传输模式指令:AT+CIPSEND=<link_ID>,<length>;
普通传输模式预期响应:OK;
Wi-Fi透传模式指令:AT+CIPSEND=<link_ID>;
Wi-Fi透传模式预期响应:OK;
错误响应(如果连接无效):Link is not valid ERROR;
| 参数 | 描述 | 说明 |
|---|---|---|
| <link_ID> | 连接ID | - |
| <length> (仅限普通模式) | 要发送的数据长度(以字节为单位) | 最大值:2048字节(由CONFIG_AT_ CIPSEND_MAX定义) |
普通传输模式:在此模式下,您在收到 > 提示后发送数据。然后模组将通过指定的连接传输数据。
Wi-Fi透传模式: 在此模式下,串口上 > 提示后接收到的所有数据将直接通过指定的连接传输,而无需模组进行任何处理。您可以通过发送特殊指令 +++(后跟回车符)退出透传模式。
AT+CIPINFO – 设置“+IPD”消息模式
类型:设置/查询;
描述:此指令启用或禁用在+IPD非请求响应中显示详细的远程主机信息,该响应指示TCP或UDP连接上的传入数据;
设置指令:AT+CIPINFO=<mode>;
设置响应:OK;
查询指令:AT+CIPINFO?;
查询响应(启用):+CIPINFO:TRUE OK;
查询响应(禁用):+CIPINFO:FALSE OK。
参数:
<mode>:启用或禁用详细的+IPD信息。
-
0: 禁用;
-
1: 启用。
AT+CIPCLOSE – 关闭TCP/UDP/SSL连接
类型:执行;
描述:此指令关闭活动的TCP、UDP或SSL连接
指令:AT+CIPCLOSE=<link_ID>;
预期响应(成功):OK 或者 <link_ID>,CLOSE OK;
错误响应:UNLINK ERROR;
参数:<link_ID>,要关闭的连接ID。
电源管理AT指令
概述
本节介绍TLSR9118模组上与电源管理(PM)相关的AT指令。这些指令允许您控制系统级电源管理和Wi-Fi省电功能,以优化功耗。
| 指令 | 描述 |
|---|---|
| AT+PME | 启用/禁用系统电源管理 |
| AT+PMEW | 启用/禁用WLAN省电 |
指令
AT+PME – 启用/禁用系统电源管理
类型:设置;
描述:此指令启用或禁用TLSR9118模组的系统级电源管理功能;
指令:AT+PME=<enable>;
预期响应:OK。
参数:
<enable>:启用或禁用系统电源管理。
-
0: 禁用系统电源管理;
-
1: 启用系统电源管理。
注意
- 启用系统电源管理可能会在空闲时使模组进入低功耗状态,从而可能影响响应速度。
AT – 测试AT启动
类型:设置;
描述:此指令启用或禁用Wi-Fi (WLAN)接口的省电模式,启用后,Wi-Fi接口将在不活动期间进入低功耗状态以节省能源;
指令:AT+PMEW=<enable>[,<interval>];
预期响应:OK。
| 参数 | 描述 | 可能的值/说明 |
|---|---|---|
| <enable> | 启用或禁用WLAN省电 | 0: 禁用WLAN省电; 1: 启用WLAN省电。 |
| <interval> | 唤醒周期之间用于信标侦听的时间间隔(以毫秒为单位)(可选) | 范围:100-1000(默认值:100) |
注意
- <interval> 参数指定Wi-Fi接口唤醒以侦听来自接入点(AP)的信标的频率。较短的间隔可以提高响应速度,但会消耗更多电量。
调试指南
概述
调试指南提供了在应用程序开发过程中遇到的常见问题的基本调试技术。它既可作为教学工具,也可作为故障排除的快速参考。
涵盖内容
本文档重点解决以下特定类型的问题:
- 核心异常
- 断言失败
- 堆栈溢出
- 内存泄漏
此外,可能会出现诸如死锁等其他问题;然而,本文讨论的策略和工具同样适用于这些场景。对于深入调试,建议使用像AICE-MICRO这样的JTAG调试器,并结合OpenOCD。详细的设置说明可以参考SDK入门指南。
日志机制
TLSR9118 SDK提供了两种通过UART控制台进行日志记录的主要方法:
| 函数 | 头文件 | 描述 |
|---|---|---|
| int incl printf (const char *format, ...) |
ude/stdio.h - 直 - 不 | 接将字节字符串发送到UART端口。 适用于ISR上下文,因为它依赖于操作系统相关的等待函数。 - 引入显著的运行时开销。 - 不包含在核心转储中,参考章节使用ps命令。 |
| int Incl printk (const char *format, ...) |
ude/hal/console.h - 将 - 可 | 字节字符串输出到可配置的内部控制台缓冲区。 在线程和ISR上下文中使用。 - dmesgCLI命令显示整个控制台缓冲区;后续调用会清除缓冲区。- dmesg start启用实时日志记录,性能影响最小。- 包含在核心转储中,参考章节使用ps命令。 |
为了有效进行故障排除,使用printf或printk快速隔离问题,从而减少后续步骤的时间和精力。
使用ps命令
ps命令提供了系统活动任务的洞察,帮助识别与任务管理和资源利用相关的潜在问题。
命令输出:
$ ps
PID PR STWM S %CPU+ TIME+ TASK
1 3 480 X 2.3 0:00:01 init (0x21bda0-0x21cd90, 0x21c85c)
2 0 966 R 78.7 0:00:45 idle (0x20fc04-0x210c00, 0x210b1c)
3 5 199 B 18.9 0:00:10 ksofttimerd (0x20f780-0x20fb70, 0x20fa9c)
4 3 683 B 0.0 0:00:00 knetd (0x21d0b0-0x21dca0, 0x21db5c)
5 3 571 B 0.0 0:00:00 tlsr_wlan-wlan fast taskq (0x21f710-0x220100, 0x21fffc)
7 3 303 B 0.0 0:00:00 rt_msg (0x221600-0x221bf0, 0x221acc)
6 3 315 B 0.0 0:00:00 knet80211d/wlan0 (0x220d30-0x221320, 0x22121c)
8 7 48 B 0.0 0:00:00 ll (0x2222e0-0x222550, 0x22244c)
$
ps命令显示的信息如下表所述。
| 列名 | 含义 | 描述 |
|---|---|---|
| PID | 任务标识符 | 由操作系统分配。 |
| PR | 任务优先级 | 参考CMSIS-FreeRTOS_API指南. |
| STWM | 栈水位标记 | 表示观察到的最小空闲栈空间。 |
| S | 任务状态 | ‘X’:运行中 ‘R’:就绪 ‘B’:阻塞 ‘S’:挂起 ‘D’:删除 ‘I’:无效 |
| %CPU+ | 相对CPU利用率 | CPU利用率百分比(相对) |
| TIME+ | 任务消耗的绝对CPU时间 | CPU利用率时间(绝对) |
| TASK | 任务名称 | 创建时指定 |
| Stack | (Start-End, sp) | 见下文。 |
此信息提供了当前系统状态的快照,有助于有效诊断问题。
括号中的三个十六进制数分别表示任务栈的起始地址、结束地址和最后记录的栈指针。
例如,'init'任务的栈位于0x21bda0到0x21cd90之间,最后记录的栈指针为0x21c85c。
'STWM'(栈水位标记)列反映了任务在其最资源密集型操作期间可用的最小空闲空间(以uint32为单位)。此列中的较低值表明栈溢出的风险较高。
“最后记录的栈指针”指的是操作系统(在此情况下为FreeRTOS)在上下文切换期间保存的栈指针值,例如在任务之间或从任务切换到中断时。ps命令显示此值,提供了每个任务栈使用模式的洞察。
尽管“最后记录的栈指针”不提供实时栈指针值,尤其是对于当前正在运行的任务,但它在调试中非常有用。分析栈的最后已知状态的内存内容可以揭示在切换或错误发生之前任务状态的关键信息。
为了进一步了解'init'任务在最后一次上下文切换时的栈状态,我们可以如下检查内存:
$ hexdump 0x21c85c 64
0x0021c850: .... .... .... .... .... .... 4ac7 1000 ............J
0x0021c860: 4000 0000 0000 0000 a5a5 a5a5 0ec8 1000 @
0x0021c870: a500 0000 a0bd 2100 90cd 2100 f02c 2200 ! ! ,"
0x0021c880: 2800 0000 0000 0000 708e 0980 0800 0000 ( p
0x0021c890: d000 0000 0000 0000 082e 2200 .... .... ." ....
$
请注意,hexdump命令将以字节为单位显示内存内容,保持字节顺序。相比之下,read命令以字为单位显示数据,每个字由32位组成。这种格式特别有助于理解系统处理数据的结构。
read命令的示例输出如下:
$ read 0x21c85c 16
[0x21c85c] 0x0010c74a
[0x21c860] 0x00000040
[0x21c864] 0x00000000
[0x21c868] 0xa5a5a5a5
[0x21c86c] 0x0010c80e
[0x21c870] 0x000000a5
[0x21c874] 0x0021bda0
[0x21c878] 0x0021cd90
[0x21c87c] 0x00222cf0
[0x21c880] 0x00000028
[0x21c884] 0x00000000
[0x21c888] 0x80098e70
[0x21c88c] 0x00000008
[0x21c890] 0x000000d0
[0x21c894] 0x00000000
[0x21c898] 0x00222e08
$
此输出提供了特定地址处内存中数据结构的清晰视图,这对于调试和跟踪系统中的问题至关重要。
理解如何从栈转储中推导调用流涉及分析此类内存内容,以回溯导致当前状态的函数调用,此过程将在本文档后面详细说明。此分析有助于识别导致异常或崩溃的事件或错误序列,从而帮助准确定位问题的根源。
核心转储
当发生陷阱异常或断言失败时,会自动生成核心转储。它提供了异常发生时内存和处理器状态的详细快照,这对于调试至关重要。核心转储直接输出到UART控制台,允许开发人员分析导致失败的条件。
核心转储示例:
WITS 2018.02+ riscv32-elf-gcc.gnu (2022-02-07_nds32le-elf-mculib-v5-86807094a2f) 10.3.0
[000361.560214] BOARD: tlsr_w91 QFN40 EVB V1.0
[000361.560368] VFS: filesystem devfs mounted onto /dev
[000361.562230] PINCTRL: pin controller tlsr_w91,pinctrl registered
[000361.562440] GPIO: tlsr_w91,pinctrl registered as /dev/gpio
[000361.562728] atcwdt: @0xf1300000, clk=32768
[000361.562936] WDT: atcwdt registered as /dev/watchdog
[000361.563279] PINCTRL: atcspi200-xip/cs request pin 11
[000361.563471] PINCTRL: atcspi200-xip/clk request pin 12
[000361.563636] PINCTRL: atcspi200-xip/mosi request pin 13
[000361.563808] PINCTRL: atcspi200-xip/miso request pin 14
[000361.563993] PINCTRL: atcspi200-xip/hold request pin 9
[000361.564173] PINCTRL: atcspi200-xip/wp request pin 10
[000361.564667] EFUSE: efuse-tlsr_w91 registered as /dev/efuse
[000361.564799] trng version : 5e5e0010
[000361.564962] TRNG: trng registered as /dev/trng
[000361.565084] pke version : 0x5e5e0010
[000361.607174] Use fixed MAC address: 64.f9.47.f0.01.20
[000361.608169] 261 usec elapsed in downloading MAC FW.
[000362.902695] Wlan PM ctx init done
[000362.904031] UART: atcuart.0 registered as /dev/ttyS0
[000362.904346] CONSOLE: add /dev/ttyS0
[000362.904664] UART: atcuart.1 registered as /dev/ttyS1
[000362.904788] SOC: tlsr_w91
[000362.905306] Use fixed BLE public address: 01.02.03.04.05.06
[000362.907563] ble phy init 35
[000362.907711] PM feature : 0x1d
[000362.908487] PM LS : 6500+1120=7620
[000362.908655] PM SL : 6500+7100=13600
[000362.908817] PM DS : 11500+12800=24300
[000362.908990] PM HB : 1655000+60000=1715000
[000362.909248] PINCTRL: atcuart.0/txd request pin 22
[000362.909412] PINCTRL: atcuart.0/rxd request pin 21
[000377.371510] Unhandled Trap : Ilegal instruction (mcause = 0x2), mepc = 0xc0000000
[000377.379216] EPC:c0000000
[000377.381940] MSTATUS:00001880
[000377.385019] MXSTATUS:00000080
[000377.388188] MTVAL:00003003
[000377.391092] A0:c0000000 A1:00000000 A2:00000010 A3:00000001 A4:00000002 A5:ffffffff A6:00000008 A7:00210f37
[000377.401165] T0:8008e6f8 T1:001096f0 T2:00000000 T3:00210f2c T4:000001b0 T5:8008e71c T6:00000008
[000377.410197] S0:00000002 S1:0020d488 S2:ffffffff S3:00000001 S4:8010a220 S5:a5a5a5a5 S6:a5a5a5a5 S7:a5a5a5a5
[000377.420373] S8:a5a5a5a5 S9:a5a5a5a5 S10:a5a5a5a5 S11:a5a5a5a5
[000377.426377] FP:00000002 RA:8008e6fe
[000377.429989] sp: 0021cb6c
[000377.432977] IRQ stack:
[000377.435443] base: 0021b590
[000377.438433] size: 00000800
[000377.441429] ERROR: Stack pointer is not within the interrupt stack
[000377.447766] 0021b580: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.456531] 0021b5a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.465294] 0021b5c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.474056] 0021b5e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.482819] 0021b600: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.491582] 0021b620: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.500345] 0021b640: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.509108] 0021b660: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.517871] 0021b680: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.526634] 0021b6a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.535397] 0021b6c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.544160] 0021b6e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.552923] 0021b700: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.561686] 0021b720: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.570448] 0021b740: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.579211] 0021b760: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.587974] 0021b780: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.596737] 0021b7a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.605500] 0021b7c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.614263] 0021b7e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.623026] 0021b800: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.631789] 0021b820: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.640551] 0021b840: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.649314] 0021b860: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.658077] 0021b880: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.666840] 0021b8a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.675603] 0021b8c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.684366] 0021b8e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.693129] 0021b900: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.701892] 0021b920: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.710655] 0021b940: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.719417] 0021b960: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.728180] 0021b980: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.736943] 0021b9a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.745706] 0021b9c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.754469] 0021b9e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.763231] 0021ba00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.771994] 0021ba20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.780757] 0021ba40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.789520] 0021ba60: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.798283] 0021ba80: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.807046] 0021baa0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.815809] 0021bac0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.824572] 0021bae0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.833334] 0021bb00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.842097] 0021bb20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.850860] 0021bb40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.859623] 0021bb60: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.868386] 0021bb80: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.877149] 0021bba0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.885911] 0021bbc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.894674] 0021bbe0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.903437] 0021bc00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.912200] 0021bc20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.920963] 0021bc40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.929726] 0021bc60: 00000000 00000000 00000000 00000000 0020c310 00203bf4 0020c9b8 80100c3a
[000377.938604] 0021bc80: 0020c310 00203bf4 0020c9b8 00000000 a5a5a5a5 a5a5a5a5 a5a5a5a5 0021bd2c
[000377.947582] 0021bca0: 00000005 00000000 00000100 8010b5ec 0010d29c 0020f780 0020fb80 8008e8e8
[000377.956508] 0021bcc0: a5a5a5a5 8000bad4 0020fb80 00000084 a5a5a5a5 8000bad4 00000001 ffffffff
[000377.965486] 0021bce0: 80080020 0020d5f8 0020d5f8 8008eaa6 a5a5a5a5 0020fb80 0020f780 00000380
[000377.974475] 0021bd00: 80080020 0020d5f8 0020d5f8 0021cdc8 00000000 ffffffff 00222c20 0010a9c6
[000377.983442] 0021bd20: 80122218 00000000 e0871aa7 00215798 00000004 0020dea8 00223500 80100878
[000377.992394] 0021bd40: 000048e2 00203bf4 0010d8d4 00206ef4 00000009 00000000 0010b70e 0000001c
[000378.001283] 0021bd60: 167be6a8 00000000 002228e0 000002a0 00003d2a 00000009 00206ef4 0010d8d4
[000378.010177] 0021bd80: 00203bf4 000048e2 00000000 00000414 00000000 80001010 00000000 00000000
[000378.019024] sp: 0021cbfc
[000378.022018] User stack:
[000378.024568] base: 0021bda0
[000378.027562] size: 00000ff0
[000378.030556] 0021cbe0: 002065d4 c0000000 00000002 ffffffff 0020d488 00000002 8008e6fe c0000000
[000378.039515] 0021cc00: 00000080 00003003 00206658 8008e6fe 8008e6f8 001096f0 00000000 00000002
[000378.048420] 0021cc20: 0020d488 c0000000 00000000 00000010 00000001 00000002 ffffffff 00000008
[000378.057283] 0021cc40: 00210f37 ffffffff 00000001 8010a220 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5
[000378.066287] 0021cc60: a5a5a5a5 a5a5a5a5 a5a5a5a5 00210f2c 000001b0 8008e71c 00000008 00001880
[000378.075244] 0021cc80: a5a5a5a5 a5a5a5a5 8010a220 c0000000 ffffffff 0020d488 00000002 8008e742
[000378.084249] 0021cca0: ffffffff 0020d488 00000002 800981cc e2a0c53d 00210d70 0020d3f8 00210f37
[000378.093222] 0021ccc0: 00210f20 00210f2d 00000000 00000000 00000000 00000000 00000000 00000000
[000378.102032] 0021cce0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000378.110795] 0021cd00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000378.119558] 0021cd20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000378.128321] 0021cd40: a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 80113eac 00210f20 800985b2
[000378.137367] 0021cd60: a5a5a5a5 0020d5fc 00202c04 800985c6 a5a5a5a5 0020d5fc 00202c04 800ff356
[000378.146382] 0021cd80: a5a5a5a5 a5a5a5a5 a5a5a5a5 00000000 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5
[000378.159763] PID PR STWM S TASK
[000378.167018] 1 3 531 X init (0x21bda0-0x21cd90, 0x21c6f8)
[000378.175996] 2 0 958 R idle (0x20fc04-0x210c00, 0x210b1c)
[000378.184966] 4 3 683 B knetd (0x21d0b0-0x21dca0, 0x21db5c)
[000378.193936] 3 5 199 B ksofttimerd (0x20f780-0x20fb70, 0x20fa9c)
[000378.202906] 8 7 48 B ll (0x2223f0-0x222660, 0x22255c)
[000378.211871] 5 3 571 B tlsr_wlan-wlan fast taskq (0x21f710-0x220100, 0x21fffc)
[000378.220841] 7 3 303 B rt_msg (0x221600-0x221bf0, 0x221acc)
[000378.229811] 6 3 315 B knet80211d/wlan0 (0x220d30-0x221320, 0x22121c)
核心转储由多个部分组成,每个部分提供了在失败时系统状态的关键信息。下面是核心转储典型组成部分的概述,按其通常出现的顺序排列:
控制台缓冲区内容
在核心转储的开始部分,控制台缓冲区的内容被刷新并显示出来。该内容提供了导致核心转储的系统活动日志,如果使用了printk函数进行调用流跟踪,则可以帮助进行事后调试过程。
控制台输出示例:
WITS 2018.02+ riscv32-elf-gcc.gnu (2022-02-07_nds32le-elf-mculib-v5-86807094a2f) 10.3.0
[000361.560214] BOARD: tlsr_w91 QFN40 EVB V1.0
[000361.560368] VFS: filesystem devfs mounted onto /dev
[000361.562230] PINCTRL: pin controller tlsr_w91,pinctrl registered
[000361.562440] GPIO: tlsr_w91,pinctrl registered as /dev/gpio
[000361.562728] atcwdt: @0xf1300000, clk=32768
[000361.562936] WDT: atcwdt registered as /dev/watchdog
[000361.563279] PINCTRL: atcspi200-xip/cs request pin 11
[000361.563471] PINCTRL: atcspi200-xip/clk request pin 12
[000361.563636] PINCTRL: atcspi200-xip/mosi request pin 13
[000361.563808] PINCTRL: atcspi200-xip/miso request pin 14
[000361.563993] PINCTRL: atcspi200-xip/hold request pin 9
[000361.564173] PINCTRL: atcspi200-xip/wp request pin 10
[000361.564667] EFUSE: efuse-tlsr_w91 registered as /dev/efuse
[000361.564799] trng version : 5e5e0010
[000361.564962] TRNG: trng registered as /dev/trng
[000361.565084] pke version : 0x5e5e0010
[000361.607174] Use fixed MAC address: 64.f9.47.f0.01.20
[000361.608169] 261 usec elapsed in downloading MAC FW.
[000362.902695] Wlan PM ctx init done
[000362.904031] UART: atcuart.0 registered as /dev/ttyS0
[000362.904346] CONSOLE: add /dev/ttyS0
[000362.904664] UART: atcuart.1 registered as /dev/ttyS1
[000362.904788] SOC: tlsr_w91
[000362.905306] Use fixed BLE public address: 01.02.03.04.05.06
[000362.907563] ble phy init 35
[000362.907711] PM feature : 0x1d
[000362.908487] PM LS : 6500+1120=7620
[000362.908655] PM SL : 6500+7100=13600
[000362.908817] PM DS : 11500+12800=24300
[000362.908990] PM HB : 1655000+60000=1715000
[000362.909248] PINCTRL: atcuart.0/txd request pin 22
[000362.909412] PINCTRL: atcuart.0/rxd request pin 21
核心转储原因
核心转储的原因在转储中明确列出,指示是否是由于陷阱异常或断言失败引起的。会显示具体的错误描述,以及来自mcause和mepc寄存器的相关代码,以澄清转储的原因。
原因输出示例:
[000377.371510] Unhandled Trap : Ilegal instruction (mcause = 0x2), mepc = 0xc0000000
核心寄存器转储
本节提供了在崩溃时的通用寄存器(GPRs)和控制状态寄存器(CSRs)的完整列表。这些数据对于调试至关重要,因为它有助于重建应用程序在失败时的状态。
原因输出示例:
[000377.379216] EPC:c0000000
[000377.381940] MSTATUS:00001880
[000377.385019] MXSTATUS:00000080
[000377.388188] MTVAL:00003003
[000377.391092] A0:c0000000 A1:00000000 A2:00000010 A3:00000001 A4:00000002 A5:ffffffff A6:00000008 A7:00210f37
[000377.401165] T0:8008e6f8 T1:001096f0 T2:00000000 T3:00210f2c T4:000001b0 T5:8008e71c T6:00000008
[000377.410197] S0:00000002 S1:0020d488 S2:ffffffff S3:00000001 S4:8010a220 S5:a5a5a5a5 S6:a5a5a5a5 S7:a5a5a5a5
[000377.420373] S8:a5a5a5a5 S9:a5a5a5a5 S10:a5a5a5a5 S11:a5a5a5a5
[000377.426377] FP:00000002 RA:8008e6fe
中断栈转储
该部分包含中断栈的内容。即使栈指针(sp)不在中断栈内,仍会转储其完整内容以进行彻底分析。
原因输出示例:
[000377.429989] sp: 0021cb6c
[000377.432977] IRQ stack:
[000377.435443] base: 0021b590
[000377.438433] size: 00000800
[000377.441429] ERROR: Stack pointer is not within the interrupt stack
[000377.447766] 0021b580: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.456531] 0021b5a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.465294] 0021b5c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.474056] 0021b5e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.482819] 0021b600: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.491582] 0021b620: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.500345] 0021b640: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.509108] 0021b660: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.517871] 0021b680: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.526634] 0021b6a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.535397] 0021b6c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.544160] 0021b6e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.552923] 0021b700: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.561686] 0021b720: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.570448] 0021b740: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.579211] 0021b760: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.587974] 0021b780: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.596737] 0021b7a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.605500] 0021b7c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.614263] 0021b7e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.623026] 0021b800: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.631789] 0021b820: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.640551] 0021b840: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.649314] 0021b860: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.658077] 0021b880: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.666840] 0021b8a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.675603] 0021b8c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.684366] 0021b8e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.693129] 0021b900: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.701892] 0021b920: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.710655] 0021b940: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.719417] 0021b960: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.728180] 0021b980: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.736943] 0021b9a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.745706] 0021b9c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.754469] 0021b9e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.763231] 0021ba00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.771994] 0021ba20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.780757] 0021ba40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.789520] 0021ba60: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.798283] 0021ba80: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.807046] 0021baa0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.815809] 0021bac0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.824572] 0021bae0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.833334] 0021bb00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.842097] 0021bb20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.850860] 0021bb40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.859623] 0021bb60: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.868386] 0021bb80: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.877149] 0021bba0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.885911] 0021bbc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.894674] 0021bbe0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.903437] 0021bc00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.912200] 0021bc20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.920963] 0021bc40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000377.929726] 0021bc60: 00000000 00000000 00000000 00000000 0020c310 00203bf4 0020c9b8 80100c3a
[000377.938604] 0021bc80: 0020c310 00203bf4 0020c9b8 00000000 a5a5a5a5 a5a5a5a5 a5a5a5a5 0021bd2c
[000377.947582] 0021bca0: 00000005 00000000 00000100 8010b5ec 0010d29c 0020f780 0020fb80 8008e8e8
[000377.956508] 0021bcc0: a5a5a5a5 8000bad4 0020fb80 00000084 a5a5a5a5 8000bad4 00000001 ffffffff
[000377.965486] 0021bce0: 80080020 0020d5f8 0020d5f8 8008eaa6 a5a5a5a5 0020fb80 0020f780 00000380
[000377.974475] 0021bd00: 80080020 0020d5f8 0020d5f8 0021cdc8 00000000 ffffffff 00222c20 0010a9c6
[000377.983442] 0021bd20: 80122218 00000000 e0871aa7 00215798 00000004 0020dea8 00223500 80100878
[000377.992394] 0021bd40: 000048e2 00203bf4 0010d8d4 00206ef4 00000009 00000000 0010b70e 0000001c
[000378.001283] 0021bd60: 167be6a8 00000000 002228e0 000002a0 00003d2a 00000009 00206ef4 0010d8d4
[000378.010177] 0021bd80: 00203bf4 000048e2 00000000 00000414 00000000 80001010 00000000 00000000
用户栈转储
用户栈部分包括从栈底到当前栈指针(sp)的详细内存内容。该信息对于调试尤其重要,因为它通常包含导致核心转储的调用流和其他关键数据。
原因输出示例:
[000378.019024] sp: 0021cbfc
[000378.022018] User stack:
[000378.024568] base: 0021bda0
[000378.027562] size: 00000ff0
[000378.030556] 0021cbe0: 002065d4 c0000000 00000002 ffffffff 0020d488 00000002 8008e6fe c0000000
[000378.039515] 0021cc00: 00000080 00003003 00206658 8008e6fe 8008e6f8 001096f0 00000000 00000002
[000378.048420] 0021cc20: 0020d488 c0000000 00000000 00000010 00000001 00000002 ffffffff 00000008
[000378.057283] 0021cc40: 00210f37 ffffffff 00000001 8010a220 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5
[000378.066287] 0021cc60: a5a5a5a5 a5a5a5a5 a5a5a5a5 00210f2c 000001b0 8008e71c 00000008 00001880
[000378.075244] 0021cc80: a5a5a5a5 a5a5a5a5 8010a220 c0000000 ffffffff 0020d488 00000002 8008e742
[000378.084249] 0021cca0: ffffffff 0020d488 00000002 800981cc e2a0c53d 00210d70 0020d3f8 00210f37
[000378.093222] 0021ccc0: 00210f20 00210f2d 00000000 00000000 00000000 00000000 00000000 00000000
[000378.102032] 0021cce0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000378.110795] 0021cd00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000378.119558] 0021cd20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000378.128321] 0021cd40: a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 80113eac 00210f20 800985b2
[000378.137367] 0021cd60: a5a5a5a5 0020d5fc 00202c04 800985c6 a5a5a5a5 0020d5fc 00202c04 800ff356
[000378.146382] 0021cd80: a5a5a5a5 a5a5a5a5 a5a5a5a5 00000000 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5
活动任务列表
最后,活动任务列表提供了系统中所有正在运行和被阻塞任务的快照,类似于ps命令的输出。这有助于识别在崩溃时哪些任务是活动的,以及它们的栈是如何被使用的。
任务列表示例:
[000378.159763] PID PR STWM S TASK
[000378.167018] 1 3 531 X init (0x21bda0-0x21cd90, 0x21c6f8)
[000378.175996] 2 0 958 R idle (0x20fc04-0x210c00, 0x210b1c)
[000378.184966] 4 3 683 B knetd (0x21d0b0-0x21dca0, 0x21db5c)
[000378.193936] 3 5 199 B ksofttimerd (0x20f780-0x20fb70, 0x20fa9c)
[000378.202906] 8 7 48 B ll (0x2223f0-0x222660, 0x22255c)
[000378.211871] 5 3 571 B tlsr_wlan-wlan fast taskq (0x21f710-0x220100, 0x21fffc)
[000378.220841] 7 3 303 B rt_msg (0x221600-0x221bf0, 0x221acc)
[000378.229811] 6 3 315 B knet80211d/wlan0 (0x220d30-0x221320, 0x22121c)
当交互式 CLI 无法使用时,可以使用 JTAG 调试器等工具进一步调查活动线程的最后栈内容,从而深入了解导致系统故障的情况。
反汇编器使用
分析核心转储中的栈内容或hexdump命令的输出时,需要对可执行文件进行反汇编,本文将其称为“wits”,以理解程序的指令流。
有两个主要的反汇编输出与调试相关:
| 项目 | 内容 | 备注 |
|---|---|---|
| wits.dis | 这是“wits”可执行文件的反汇编输出。 | 生成: 可以使用命令make wits.dis创建。内容: 仅包含可执行代码,不包括位于 ROM 库中的任何指令(*)。 |
| wits_rom.dis | 这是ROM库的反汇编输出。 | 位置: 位于 /hal/soc/tlsr_w91。 内容: 仅包含 ROM(只读内存)的一部分指令。 |
(*) ROM库是嵌入在TLSR9118内部ROM中的软件库,对于频繁访问或对性能至关重要的操作非常关键。
wits.dis 示例
wits.dis 的开头显示了初始化系统和设置环境的汇编指令,这对于启动过程至关重要:
wits: file format elf32-littleriscv
Disassembly of section .text.boot:
80080020 <_start>:
_start:
/* Initialize global pointer */
.option push
.option norelax
la gp, __global_pointer$
80080020: 8018b197 auipc gp,0x8018b
80080024: 3e018193 addi gp,gp,992 # 20b400 <__global_pointer$>
.option pop
/* Initialize stack pointer */
la t0, _stack
80080028: 8019c297 auipc t0,0x8019c
8008002c: d6828293 addi t0,t0,-664 # 21bd90 <__stack_end>
mv sp, t0
80080030: 8116 c.mv sp,t0
/* Initialize FCSR */
fscsr zero
#endif
/* Disable all interrupts (i.e. timer, external) in mie */
csrw mie, zero
80080032: 30401073 csrw mie,zero
/* Initial machine trap-vector Base. Use FreeRTOS trap function. */
la t0, freertos_risc_v_trap_handler
80080036: 7ff80297 auipc t0,0x7ff80
8008003a: 34a28293 addi t0,t0,842 # 380 <__heapext2_end+0x5fdd0380>
csrw mtvec, t0
8008003e: 30529073 csrw mtvec,t0
wits_rom.dis示例
wits_rom.dis提供了ROM部分的反汇编内容,展示了内置库函数是如何实现的:
wits_rom: file format elf32-littleriscv
Disassembly of section .rom.text:
00106000 <rom_version>:
106000: 01 00 00 00 01 00 00 00 ........
00106008 <abort>:
106008: 00004317 auipc t1,0x4
10600c: e2a302e7 jalr t0,-470(t1) # 109e32 <__riscv_save_0>
106010: 4505 c.li a0,1
106012: 00004097 auipc ra,0x4
106016: 04a080e7 jalr 74(ra) # 10a05c <_exit>
0010601a <abs>:
10601a: 41f55793 srai a5,a0,0x1f
10601e: 8d3d c.xor a0,a5
106020: 8d1d c.sub a0,a5
106022: 8082 c.jr ra
00106024 <atoi>:
106024: 4581 c.li a1,0
106026: 4629 c.li a2,10
106028: 00002317 auipc t1,0x2
10602c: b8430067 jr -1148(t1) # 107bac <strtol>
106030: 0001 c.nop
...
00106034 <bcopy>:
106034: 86aa c.mv a3,a0
106036: 852e c.mv a0,a1
106038: 85b6 c.mv a1,a3
10603a: 00000317 auipc t1,0x0
10603e: 35630067 jr 854(t1) # 106390 <memmove>
...
00106044 <bzero>:
106044: 862e c.mv a2,a1
106046: 4581 c.li a1,0
106048: 00000317 auipc t1,0x0
10604c: 42430067 jr 1060(t1) # 10646c <memset>
106050: 0001 c.nop
任何典型的调用流程都将包括在wits-sdk中构建的函数和已经放入TLSR9118 ROM中的函数(即指令),它们通过跳转表连接在一起,这也是为什么很难提供一个统一、易于使用的调试环境或工具,来同时涵盖已加载和预安装的指令和数据。更复杂的是,这些跳转表将在运行时更新,例如,用于修补ROM库函数。
值得注意的是,以独立模式构建的可执行文件(即使用tlsr9118s_defconfig或其衍生配置)将从目标内存空间的三个不同位置读取指令。
| 内存 | 位置地址 | 范围备注 |
|---|---|---|
| ILM 0x00 (指令本地内存) |
000000 – 0x00007fff - 32 | KB |
| XIP flash (*) | 0x80080000 – 0x8013ffff or 或<b 0x80080000 – 0x801fffff |
- 768KB r> - 1536KB |
| ROM | 0x106000 – 0x153fff | - 312KB - 内置于TLSR9118 - 可修补 |
(*) 默认位置,大小有两个选项。
参考这些地址范围可以加速分析栈内容,从而找出调用流程,因为可以仅关注适合指令的地址。
演示:如何使用TLSR9118 SDK进行调试
概述
本节提供了如何使用TLSR9118 SDK中包含的“如何调试”演示应用程序的逐步指南。该演示旨在帮助理解与常见问题(如核心异常、断言失败、堆栈溢出和内存泄漏)相关的各种调试技术。
准备演示环境
(1) 设置构建配置。首先配置构建环境,将“如何调试”演示应用程序设置为主应用:
- $ make tlsr9118s_defconfig
- $ make menuconfig
(2) 选择演示应用程序
- 在菜单配置中导航到
Applications -> How-to-debug demo。 - 确认选择,退出配置菜单并保存更改。
(3) 选择调试场景
根据要演示的特定问题,在Applications -> How-to-debug demo configuration -> Type of debugging下选择适当的调试类型:

选择不同的选项将需要参考以下章节。
| 选项 | 对应章节 |
|---|---|
| 调试核心异常 | 核心异常 |
| 调试断言失败 | 断言失败 |
| 调试堆栈溢出 | 堆栈溢出 |
| 调试内存(堆)泄漏 | 内存泄漏 |
退出菜单并保存配置。
构建演示程序
- 构建可执行二进制文件:$ make
注意
- 根据选择的问题类型,可能会遇到构建错误。如果发生这种情况,请查看提供的错误信息,并根据建议调整配置以启用额外的功能。
运行演示程序
-
下载并运行应用程序:
- 按照SDK入门指南中的说明,将
wits.mcuboot.bin下载并烧录到TLSR9118评估板(EVK)。 - 观察演示的行为,展示所选的调试问题是如何表现出来的,并如何使用本指南中描述的技术进行诊断。
- 按照SDK入门指南中的说明,将
核心异常
概述
TLSR9118中的核心异常可能由各种问题引起,可以通过mcause寄存器识别。该寄存器通过提供特定的异常代码,帮助诊断异常的确切原因。
异常代码和描述
以下是mcause寄存器中常见异常代码及其含义的总结:
| mcause[11:0] | 描述 |
|---|---|
| 0 | 指令地址未对齐 |
| 1 | 指令访问错误 |
| 2 | 非法指令 |
| 3 | 中断点 |
| 4 | 加载地址未对齐 |
| 5 | 加载访问错误 |
| 6 | 存储/AMO地址未对齐 |
| 7 | 存储/AMO访问错误 |
| 8 | 来自用户模式的环境调用 |
TLSR9118 SDK包含一个专门的演示应用程序,旨在演示如何处理非法指令、指令访问错误和中断点异常。通过一个名为jump_to_func的CLI命令实现,模拟跳转到特定指令地址以触发这些异常。

jump_to_func工作原理
jump_to_func是一个简单的CLI命令,允许用户跳转到任意指令地址,从而模拟核心异常。这个命令特别适用于教育和调试目的,因为它可以直接引发错误条件。
触发不同的异常
通过传递不同的无效地址,可以使用jump_to_func触发特定的异常。
非法指令异常
将0xc0000000传递给jump_to_func命令将导致“非法指令”异常。
$ jump_to_func
Usage: jump_to_func <address of the function in hex.>
$
$ jump_to_func 0xc0000000
WITS 2018.02+ riscv32-elf-gcc.gnu (2022-02-07_nds32le-elf-mculib-v5-86807094a2f) 10.3.0
[003086.625667] BOARD: tlsr_w91 QFN40 EVB V1.0
[003086.625827] VFS: filesystem devfs mounted onto /dev
[003086.627695] PINCTRL: pin controller tlsr_w91,pinctrl registered
[003086.627906] GPIO: tlsr_w91,pinctrl registered as /dev/gpio
[003086.628180] atcwdt: @0xf1300000, clk=32768
[003086.628394] WDT: atcwdt registered as /dev/watchdog
[003086.628747] PINCTRL: atcspi200-xip/cs request pin 11
[003086.628947] PINCTRL: atcspi200-xip/clk request pin 12
[003086.629118] PINCTRL: atcspi200-xip/mosi request pin 13
[003086.629297] PINCTRL: atcspi200-xip/miso request pin 14
[003086.629488] PINCTRL: atcspi200-xip/hold request pin 9
[003086.629675] PINCTRL: atcspi200-xip/wp request pin 10
[003086.630177] EFUSE: efuse-tlsr_w91 registered as /dev/efuse
[003086.630314] trng version : 5e5e0010
[003086.630484] TRNG: trng registered as /dev/trng
[003086.630612] pke version : 0x5e5e0010
[003086.672324] Use fixed MAC address: 64.f9.47.f0.01.20
[003086.673338] 264 usec elapsed in downloading MAC FW.
[003087.967932] Wlan PM ctx init done
[003087.969345] UART: atcuart.0 registered as /dev/ttyS0
[003087.969499] CONSOLE: add /dev/ttyS0
[003087.969953] UART: atcuart.1 registered as /dev/ttyS1
[003087.970085] SOC: tlsr_w91
[003087.970611] Use fixed BLE public address: 01.02.03.04.05.06
[003087.972878] ble phy init 35
[003087.973030] PM feature : 0x1d
[003087.973811] PM LS : 6500+1120=7620
[003087.973979] PM SL : 6500+7100=13600
[003087.974147] PM DS : 11500+12800=24300
[003087.974326] PM HB : 1655000+60000=1715000
[003087.974589] PINCTRL: atcuart.0/txd request pin 22
[003087.974759] PINCTRL: atcuart.0/rxd request pin 21
[002333.517925] Unhandled Trap : Ilegal instruction (mcause = 0x2), mepc = 0xc0000000
[002333.525639] EPC:c0000000
[002333.528363] MSTATUS:00001880
[002333.531442] MXSTATUS:00000080
[002333.534611] MTVAL:00003003
[002333.537515] A0:c0000000 A1:00000000 A2:00000010 A3:00000001 A4:00000002 A5:ffffffff A6:00000008 A7:00210f37
[002333.547596] T0:8008e6f8 T1:001096f0 T2:00000000 T3:00210f2c T4:000001b0 T5:8008e71c T6:00000008
[002333.556635] S0:00000002 S1:0020d488 S2:ffffffff S3:00000001 S4:8010a220 S5:a5a5a5a5 S6:a5a5a5a5 S7:a5a5a5a5
[002333.566817] S8:a5a5a5a5 S9:a5a5a5a5 S10:a5a5a5a5 S11:a5a5a5a5
[002333.572828] FP:00000002 RA:8008e6fe
[002333.576440] sp: 0021cb6c
[002333.579428] IRQ stack:
[002333.581894] base: 0021b590
[002333.584884] size: 00000800
[002333.587880] ERROR: Stack pointer is not within the interrupt stack
[002333.594217] 0021b580: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.602990] 0021b5a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.611760] 0021b5c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.620529] 0021b5e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.629299] 0021b600: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.638068] 0021b620: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.646838] 0021b640: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.655607] 0021b660: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.664376] 0021b680: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.673146] 0021b6a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.681915] 0021b6c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.690685] 0021b6e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.699454] 0021b700: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.708223] 0021b720: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.716993] 0021b740: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.725762] 0021b760: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.734531] 0021b780: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.743300] 0021b7a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.752070] 0021b7c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.760839] 0021b7e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.769608] 0021b800: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.778378] 0021b820: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.787147] 0021b840: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.795917] 0021b860: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.804686] 0021b880: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.813455] 0021b8a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.822225] 0021b8c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.830994] 0021b8e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.839763] 0021b900: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.848533] 0021b920: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.857302] 0021b940: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.866071] 0021b960: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.874841] 0021b980: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.883610] 0021b9a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.892379] 0021b9c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.901149] 0021b9e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.909918] 0021ba00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.918687] 0021ba20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.927457] 0021ba40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.936226] 0021ba60: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.944996] 0021ba80: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.953765] 0021baa0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.962534] 0021bac0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.971304] 0021bae0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.980073] 0021bb00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.988842] 0021bb20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002333.997612] 0021bb40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002334.006381] 0021bb60: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002334.015140] 0021bb80: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002334.023904] 0021bba0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002334.032668] 0021bbc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002334.041432] 0021bbe0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002334.050196] 0021bc00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002334.058961] 0021bc20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002334.067725] 0021bc40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002334.076489] 0021bc60: 00000000 00000000 00000000 00000000 0020c310 00203bf4 0020c9b8 80100c3a
[002334.085369] 0021bc80: 0020c310 00203bf4 0020c9b8 00000000 a5a5a5a5 a5a5a5a5 a5a5a5a5 0021bd2c
[002334.094348] 0021bca0: 00000005 00000000 00000100 8010b5ec 0010d29c 0020f780 0020fb80 8008e8e8
[002334.103275] 0021bcc0: a5a5a5a5 a5a5a5a5 0020fb80 00000084 a5a5a5a5 a5a5a5a5 8000b660 00202bc8
[002334.112286] 0021bce0: 80080020 0020d5f8 0020d5f8 8008eaa6 a5a5a5a5 0020fb80 0020f780 00000380
[002334.121282] 0021bd00: 80080020 0020d5f8 0020d5f8 0021cdc8 00000000 ffffffff 00222bb0 0010a9c6
[002334.130256] 0021bd20: 80122218 0000002d 6e9c9552 00215798 00000004 0020dea8 00223090 80100878
[002334.139219] 0021bd40: 00361682 00203bf4 0010d8d4 00206ef4 00000009 00000000 0010b70e 0000001c
[002334.148125] 0021bd60: 8b145395 00000004 002224e0 000002a0 00360aca 00000009 00206ef4 0010d8d4
[002334.157047] 0021bd80: 00203bf4 00361682 00000000 00000414 00000000 80001010 00000000 00000000
[002334.165916] sp: 0021cbfc
[002334.168909] User stack:
[002334.171460] base: 0021bda0
[002334.174453] size: 00000ff0
[002334.177447] 0021cbe0: 002065d4 c0000000 00000002 ffffffff 0020d488 00000002 8008e6fe c0000000
[002334.186418] 0021cc00: 00000080 00003003 00206658 8008e6fe 8008e6f8 001096f0 00000000 00000002
[002334.195335] 0021cc20: 0020d488 c0000000 00000000 00000010 00000001 00000002 ffffffff 00000008
[002334.204209] 0021cc40: 00210f37 ffffffff 00000001 8010a220 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5
[002334.213225] 0021cc60: a5a5a5a5 a5a5a5a5 a5a5a5a5 00210f2c 000001b0 8008e71c 00000008 00001880
[002334.222194] 0021cc80: a5a5a5a5 a5a5a5a5 8010a220 c0000000 ffffffff 0020d488 00000002 8008e742
[002334.231211] 0021cca0: ffffffff 0020d488 00000002 800981cc 7095067b 00210d70 0020d3f8 00210f37
[002334.240195] 0021ccc0: 00210f20 00210f2d 00000000 00000000 00000000 00000000 00000000 00000000
[002334.249017] 0021cce0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002334.257787] 0021cd00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002334.266556] 0021cd20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002334.275325] 0021cd40: a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 80113eac 00210f20 800985b2
[002334.284378] 0021cd60: a5a5a5a5 0020d5fc 00202c04 800985c6 a5a5a5a5 0020d5fc 00202c04 800ff356
[002334.293400] 0021cd80: a5a5a5a5 a5a5a5a5 a5a5a5a5 00000000 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5
[002334.306791] PID PR STWM S TASK
[002334.314053] 1 3 532 X init (0x21bda0-0x21cd90, 0x21c6f8)
[002334.323036] 2 0 958 R idle (0x20fc04-0x210c00, 0x210b1c)
[002334.332012] 4 3 683 B knetd (0x21d0b0-0x21dca0, 0x21db5c)
[002334.340989] 3 5 199 B ksofttimerd (0x20f780-0x20fb70, 0x20fa9c)
[002334.349966] 6 3 315 B knet80211d/wlan0 (0x220d30-0x221320, 0x22121c)
[002334.358942] 7 3 303 B rt_msg (0x221600-0x221bf0, 0x221acc)
[002334.367915] 8 7 48 B ll (0x2227b0-0x222a20, 0x22291c)
[002334.376883] 5 3 571 B tlsr_wlan-wlan fast taskq (0x21f710-0x220100, 0x21fffc)
从核心转储中,我们可以理解:
- ‘非法指令异常’由于访问了无效地址0xc0000000而发生,并且
- ‘init’任务在此异常发生时正在运行。
分析转储的用户栈将揭示进一步的调用流信息,帮助定位导致此异常的具体原因。
请注意,堆栈是从底部向顶部增长的,即从高地址向低地址增长,这意味着位于底部的指令地址对应于调用流的开始。
[002334.165916] sp: 0021cbfc
[002334.168909] User stack:
[002334.171460] base: 0021bda0
[002334.174453] size: 00000ff0
[002334.177447] 0021cbe0: 002065d4 c0000000 00000002 ffffffff 0020d488 00000002 8008e6fe c0000000
[002334.186418] 0021cc00: 00000080 00003003 00206658 8008e6fe 8008e6f8 001096f0 00000000 00000002
[002334.195335] 0021cc20: 0020d488 c0000000 00000000 00000010 00000001 00000002 ffffffff 00000008
[002334.204209] 0021cc40: 00210f37 ffffffff 00000001 8010a220 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5
[002334.213225] 0021cc60: a5a5a5a5 a5a5a5a5 a5a5a5a5 00210f2c 000001b0 8008e71c 00000008 00001880
[002334.222194] 0021cc80: a5a5a5a5 a5a5a5a5 8010a220 c0000000 ffffffff 0020d488 00000002 8008e742
[002334.231211] 0021cca0: ffffffff 0020d488 00000002 800981cc 7095067b 00210d70 0020d3f8 00210f37
[002334.240195] 0021ccc0: 00210f20 00210f2d 00000000 00000000 00000000 00000000 00000000 00000000
[002334.249017] 0021cce0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002334.257787] 0021cd00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002334.266556] 0021cd20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[002334.275325] 0021cd40: a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 80113eac 00210f20 800985b2
[002334.284378] 0021cd60: a5a5a5a5 0020d5fc 00202c04 800985c6 a5a5a5a5 0020d5fc 00202c04 800ff356
[002334.293400] 0021cd80: a5a5a5a5 a5a5a5a5 a5a5a5a5 00000000 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5
[002334.306791] PID PR STWM S TASK
[002334.314053] 1 3 532 X init (0x21bda0-0x21cd90, 0x21c6f8)
[002334.323036] 2 0 958 R idle (0x20fc04-0x210c00, 0x210b1c)
[002334.332012] 4 3 683 B knetd (0x21d0b0-0x21dca0, 0x21db5c)
[002334.340989] 3 5 199 B ksofttimerd (0x20f780-0x20fb70, 0x20fa9c)
[002334.349966] 6 3 315 B knet80211d/wlan0 (0x220d30-0x221320, 0x22121c)
[002334.358942] 7 3 303 B rt_msg (0x221600-0x221bf0, 0x221acc)
[002334.367915] 8 7 48 B ll (0x2227b0-0x222a20, 0x22291c)
[002334.376883] 5 3 571 B tlsr_wlan-wlan fast taskq (0x21f710-0x220100, 0x21fffc)
根据章节反汇编器使用中的表格,候选的指令地址已在上文中高亮显示。在这个特定的情况下,不会涉及ILM或ROM地址,因为整个调用流都发生在XIP闪存区域。
通过查找wits.dis中的这些地址,可以揭示异常发生前的调用流如下:
(800ff356)

(800985c6)

(800985b2)

(800981cc)

(8008e742)

因此,重建的调用流为:
cli_loop -> cli_task -> cli_run_command -> cmd->handler -> jump_to_func
从这个例子可以看出,从堆栈转储中出现的指令地址总是对应于‘jalr’(即跳转并链接)或类似指令之后的指令。
这是因为‘jalr’会将返回地址(即下一条指令的地址)存储在‘ra’寄存器中,而‘ra’寄存器会在目标函数的前言中被保存到堆栈中。
(wits.dis)

(wits_rom.dis)

指令访问错误
将0x12345678传递给jump_to_func命令将导致“指令访问错误”异常。
WITS 2018.02+ (Apr 18 2024 - 10:40:27 -0700)
Exception demo.
$
$ jump_to_func
Usage: jump_to_func <address of the function in hex.>
$ jump_to_func 0x12345678
WITS 2018.02+ riscv32-elf-gcc.gnu (2022-02-07_nds32le-elf-mculib-v5-86807094a2f) 10.3.0
[000005.734989] BOARD: tlsr_w91 QFN40 EVB V1.0
[000005.735131] VFS: filesystem devfs mounted onto /dev
[000005.736982] PINCTRL: pin controller tlsr_w91,pinctrl registered
[000005.737176] GPIO: tlsr_w91,pinctrl registered as /dev/gpio
[000005.737429] atcwdt: @0xf1300000, clk=32768
[000005.737626] WDT: atcwdt registered as /dev/watchdog
[000005.737956] PINCTRL: atcspi200-xip/cs request pin 11
[000005.738139] PINCTRL: atcspi200-xip/clk request pin 12
[000005.738298] PINCTRL: atcspi200-xip/mosi request pin 13
[000005.738459] PINCTRL: atcspi200-xip/miso request pin 14
[000005.738634] PINCTRL: atcspi200-xip/hold request pin 9
[000005.738800] PINCTRL: atcspi200-xip/wp request pin 10
[000005.739284] EFUSE: efuse-tlsr_w91 registered as /dev/efuse
[000005.739406] trng version : 5e5e0010
[000005.739558] TRNG: trng registered as /dev/trng
[000005.739665] pke version : 0x5e5e0010
[000005.781668] Use fixed MAC address: 64.f9.47.f0.01.20
[000005.782652] 260 usec elapsed in downloading MAC FW.
[000007.077060] Wlan PM ctx init done
[000007.078476] UART: atcuart.0 registered as /dev/ttyS0
[000007.078614] CONSOLE: add /dev/ttyS0
[000007.079001] UART: atcuart.1 registered as /dev/ttyS1
[000007.079112] SOC: tlsr_w91
[000007.079607] Use fixed BLE public address: 01.02.03.04.05.06
[000007.081854] ble phy init 35
[000007.081975] PM feature : 0x1d
[000007.082731] PM LS : 6500+1120=7620
[000007.082885] PM SL : 6500+7100=13600
[000007.083031] PM DS : 11500+12800=24300
[000007.083189] PM HB : 1655000+60000=1715000
[000007.083429] PINCTRL: atcuart.0/txd request pin 22
[000007.083576] PINCTRL: atcuart.0/rxd request pin 21 [000020.695688] Unhandled Trap : Instruction access fault (mcause = 0x1), mepc = 0x12345678
[000020.703912] EPC:12345678
[000020.706636] MSTATUS:00001880
[000020.709714] MXSTATUS:00000080
[000020.712884] MTVAL:12345678
[000020.715788] A0:12345678 A1:00000000 A2:00000010 A3:00000001 A4:00000002 A5:ffffffff A6:00000004 A7:00210f37
[000020.725855] T0:8008e6f8 T1:001096f0 T2:00000000 T3:00210f2c T4:000001b0 T5:8008e71c T6:00000008
[000020.734882] S0:00000002 S1:0020d488 S2:ffffffff S3:00000001 S4:8010a220 S5:a5a5a5a5 S6:a5a5a5a5 S7:a5a5a5a5
[000020.745053] S8:a5a5a5a5 S9:a5a5a5a5 S10:a5a5a5a5 S11:a5a5a5a5
[000020.751052] FP:00000002 RA:8008e6fe
[000020.754664] sp: 0021cb6c
[000020.757652] IRQ stack:
[000020.760118] base: 0021b590
[000020.763108] size: 00000800
[000020.766104] ERROR: Stack pointer is not within the interrupt stack
[000020.772441] 0021b580: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.781203] 0021b5a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.789961] 0021b5c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.798718] 0021b5e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.807476] 0021b600: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.816234] 0021b620: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.824991] 0021b640: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.833749] 0021b660: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.842507] 0021b680: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.851265] 0021b6a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.860022] 0021b6c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.868780] 0021b6e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.877538] 0021b700: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.886296] 0021b720: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.895053] 0021b740: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.903811] 0021b760: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.912569] 0021b780: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.921326] 0021b7a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.930084] 0021b7c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.938842] 0021b7e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.947599] 0021b800: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.956357] 0021b820: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.965115] 0021b840: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.973872] 0021b860: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.982630] 0021b880: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000020.991388] 0021b8a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.000145] 0021b8c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.008887] 0021b8e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.017635] 0021b900: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.026387] 0021b920: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.035140] 0021b940: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.043892] 0021b960: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.052644] 0021b980: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.061397] 0021b9a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.070149] 0021b9c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.078902] 0021b9e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.087654] 0021ba00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.096407] 0021ba20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.105159] 0021ba40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.113917] 0021ba60: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.122675] 0021ba80: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.131432] 0021baa0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.140190] 0021bac0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.148948] 0021bae0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.157706] 0021bb00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.166463] 0021bb20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.175221] 0021bb40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.183979] 0021bb60: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.192736] 0021bb80: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.201494] 0021bba0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.210252] 0021bbc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.219009] 0021bbe0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.227767] 0021bc00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.236525] 0021bc20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.245283] 0021bc40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.254040] 0021bc60: 00000000 00000000 00000000 00000000 0020c310 00203bf4 0020c9b8 80100c3a
[000021.262914] 0021bc80: 0020c310 00203bf4 0020c9b8 00000000 a5a5a5a5 a5a5a5a5 a5a5a5a5 0021bd2c
[000021.271887] 0021bca0: 00000005 00000000 00000100 8010b5ec 0010d29c 0020f780 0020fb80 8008e8e8
[000021.280807] 0021bcc0: a5a5a5a5 a5a5a5a5 0020fb80 00000084 a5a5a5a5 a5a5a5a5 8000b660 00202bc8
[000021.289807] 0021bce0: 80080020 0020d5f8 0020d5f8 8008eaa6 a5a5a5a5 0020fb80 0020f780 00000380
[000021.298791] 0021bd00: 80080020 0020d5f8 0020d5f8 0021cdc8 00000000 ffffffff 00222f60 0010a9c6
[000021.307753] 0021bd20: 80122218 00000000 0bf04f4c 00215798 00000004 0020dea8 002225d0 80100878
[000021.316694] 0021bd40: 00004590 00203bf4 0010d8d4 00206ef4 00000009 00000000 0010b70e 0000001c
[000021.325578] 0021bd60: 013975b9 00000000 00222490 000002a0 000039d8 00000009 00206ef4 0010d8d4
[000021.334472] 0021bd80: 00203bf4 00004590 00000000 00000414 00000000 80001010 00000000 00000000
[000021.343319] sp: 0021cbfc
[000021.346312] User stack:
[000021.348863] base: 0021bda0
[000021.351856] size: 00000ff0
[000021.354850] 0021cbe0: 002065d4 12345678 00000001 ffffffff 0020d488 00000002 8008e6fe 12345678
[000021.363809] 0021cc00: 00000080 12345678 00206658 8008e6fe 8008e6f8 001096f0 00000000 00000002
[000021.372735] 0021cc20: 0020d488 12345678 00000000 00000010 00000001 00000002 ffffffff 00000004
[000021.381597] 0021cc40: 00210f37 ffffffff 00000001 8010a220 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5
[000021.390601] 0021cc60: a5a5a5a5 a5a5a5a5 a5a5a5a5 00210f2c 000001b0 8008e71c 00000008 00001880
[000021.399559] 0021cc80: a5a5a5a5 a5a5a5a5 8010a220 12345678 ffffffff 0020d488 00000002 8008e742
[000021.408563] 0021cca0: ffffffff 0020d488 00000002 800981cc 0e085bef 00210d70 0020d3f8 00210f37
[000021.417530] 0021ccc0: 00210f20 00210f2d 00000000 00000000 00000000 00000000 00000000 00000000
[000021.426341] 0021cce0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.435099] 0021cd00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.443856] 0021cd20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000021.452614] 0021cd40: a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 80113eac 00210f20 800985b2
[000021.461655] 0021cd60: a5a5a5a5 0020d5fc 00202c04 800985c6 a5a5a5a5 0020d5fc 00202c04 800ff356
[000021.470665] 0021cd80: a5a5a5a5 a5a5a5a5 a5a5a5a5 00000000 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5
[000021.484043] PID PR STWM S TASK
[000021.491295] 1 3 532 X init (0x21bda0-0x21cd90, 0x21c6f8)
[000021.500266] 2 0 966 R idle (0x20fc04-0x210c00, 0x210b1c)
[000021.509233] 4 3 683 B knetd (0x21d0b0-0x21dca0, 0x21db5c)
[000021.518199] 3 5 199 B ksofttimerd (0x20f780-0x20fb70, 0x20fa9c)
[000021.527165] 7 3 303 B rt_msg (0x221600-0x221bf0, 0x221acc)
[000021.536132] 6 3 315 B knet80211d/wlan0 (0x220d30-0x221320, 0x22121c)
[000021.545098] 8 7 48 B ll (0x222b60-0x222dd0, 0x222ccc)
[000021.554060] 5 3 571 B tlsr_wlan-wlan fast taskq (0x21f710-0x220100, 0x21fffc)
断点异常
将对应于ebreak指令的地址(在本例中为0x422)传递给jump_to_func命令将导致“断点”异常。
WITS 2018.02+ (Apr 18 2024 - 10:40:27 -0700)
Exception demo.
$
$
$ jump_to_func
Usage: jump_to_func <address of the function in hex.>
$
$ jump_to_func 0x422
WITS 2018.02+ riscv32-elf-gcc.gnu (2022-02-07_nds32le-elf-mculib-v5-86807094a2f) 10.3.0
[000005.734989] BOARD: tlsr_w91 QFN40 EVB V1.0
[000005.735131] VFS: filesystem devfs mounted onto /dev
[000005.736982] PINCTRL: pin controller tlsr_w91,pinctrl registered
[000005.737176] GPIO: tlsr_w91,pinctrl registered as /dev/gpio
[000005.737444] atcwdt: @0xf1300000, clk=32768
[000005.737641] WDT: atcwdt registered as /dev/watchdog
[000005.737971] PINCTRL: atcspi200-xip/cs request pin 11
[000005.738154] PINCTRL: atcspi200-xip/clk request pin 12
[000005.738313] PINCTRL: atcspi200-xip/mosi request pin 13
[000005.738474] PINCTRL: atcspi200-xip/miso request pin 14
[000005.738649] PINCTRL: atcspi200-xip/hold request pin 9
[000005.738815] PINCTRL: atcspi200-xip/wp request pin 10
[000005.739299] EFUSE: efuse-tlsr_w91 registered as /dev/efuse
[000005.739421] trng version : 5e5e0010
[000005.739573] TRNG: trng registered as /dev/trng
[000005.739680] pke version : 0x5e5e0010
[000005.781669] Use fixed MAC address: 64.f9.47.f0.01.20
[000005.782653] 260 usec elapsed in downloading MAC FW.
[000007.077076] Wlan PM ctx init done
[000007.078495] UART: atcuart.0 registered as /dev/ttyS0
[000007.078632] CONSOLE: add /dev/ttyS0
[000007.079019] UART: atcuart.1 registered as /dev/ttyS1
[000007.079130] SOC: tlsr_w91
[000007.079625] Use fixed BLE public address: 01.02.03.04.05.06
[000007.081873] ble phy init 35
[000007.081994] PM feature : 0x1d
[000007.082750] PM LS : 6500+1120=7620
[000007.082904] PM SL : 6500+7100=13600
[000007.083050] PM DS : 11500+12800=24300
[000007.083207] PM HB : 1655000+60000=1715000
[000007.083447] PINCTRL: atcuart.0/txd request pin 22
[000007.083594] PINCTRL: atcuart.0/rxd request pin 21
[000092.145991] Unhandled Trap : Breakpoint (ebreak) (mcause = 0x3), mepc = 0x422
[000092.153305] EPC:00000422
[000092.156030] MSTATUS:00001880
[000092.159108] MXSTATUS:00000080
[000092.162277] MTVAL:00000000
[000092.165182] A0:00000422 A1:00000000 A2:00000010 A3:00000001 A4:00000002 A5:ffffffff A6:00000004 A7:00210f32
[000092.175223] T0:8008e6f8 T1:001096f0 T2:00000000 T3:00210f2c T4:000001b0 T5:8008e71c T6:00000008
[000092.184250] S0:00000002 S1:0020d488 S2:ffffffff S3:00000001 S4:8010a220 S5:a5a5a5a5 S6:a5a5a5a5 S7:a5a5a5a5
[000092.194421] S8:a5a5a5a5 S9:a5a5a5a5 S10:a5a5a5a5 S11:a5a5a5a5
[000092.200420] FP:00000002 RA:8008e6fe
[000092.204032] sp: 0021cb6c
[000092.207020] IRQ stack:
[000092.209485] base: 0021b590
[000092.212476] size: 00000800
[000092.215472] ERROR: Stack pointer is not within the interrupt stack
[000092.221809] 0021b580: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.230571] 0021b5a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.239328] 0021b5c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.248086] 0021b5e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.256844] 0021b600: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.265601] 0021b620: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.274359] 0021b640: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.283117] 0021b660: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.291875] 0021b680: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.300632] 0021b6a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.309390] 0021b6c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.318148] 0021b6e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.326905] 0021b700: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.335663] 0021b720: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.344421] 0021b740: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.353179] 0021b760: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.361936] 0021b780: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.370694] 0021b7a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.379451] 0021b7c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.388209] 0021b7e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.396967] 0021b800: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.405724] 0021b820: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.414482] 0021b840: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.423240] 0021b860: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.431998] 0021b880: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.440755] 0021b8a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.449513] 0021b8c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.458271] 0021b8e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.467029] 0021b900: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.475786] 0021b920: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.484544] 0021b940: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.493302] 0021b960: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.502059] 0021b980: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.510817] 0021b9a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.519575] 0021b9c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.528332] 0021b9e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.537090] 0021ba00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.545847] 0021ba20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.554605] 0021ba40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.563363] 0021ba60: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.572121] 0021ba80: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.580878] 0021baa0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.589636] 0021bac0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.598394] 0021bae0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.607152] 0021bb00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.615909] 0021bb20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.624667] 0021bb40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.633425] 0021bb60: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.642182] 0021bb80: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.650940] 0021bba0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.659698] 0021bbc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.668455] 0021bbe0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.677213] 0021bc00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.685971] 0021bc20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.694728] 0021bc40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.703486] 0021bc60: 00000000 00000000 00000000 00000000 0020c310 00203bf4 0020c9b8 80100c3a
[000092.712359] 0021bc80: 0020c310 00203bf4 0020c9b8 00000000 a5a5a5a5 a5a5a5a5 a5a5a5a5 0021bd2c
[000092.721332] 0021bca0: 00000005 00000000 00000100 8010b5ec 0010d29c 0020f780 0020fb80 8008e8e8
[000092.730253] 0021bcc0: a5a5a5a5 a5a5a5a5 0020fb80 00000084 a5a5a5a5 a5a5a5a5 8000b660 00202bc8
[000092.739252] 0021bce0: 80080020 0020d5f8 0020d5f8 8008eaa6 a5a5a5a5 0020fb80 0020f780 00000380
[000092.748236] 0021bd00: 80080020 0020d5f8 0020d5f8 0021cdc8 00000000 ffffffff 00222f60 0010a9c6
[000092.757198] 0021bd20: 80122218 00000000 36a53b6d 00215798 00000004 0020dea8 002225d0 80100878
[000092.766144] 0021bd40: 00015caa 00203bf4 0010d8d4 00206ef4 00000009 00000000 0010b70e 0000001c
[000092.775033] 0021bd60: 057bb479 00000000 00222490 000002a0 000150f2 00000009 00206ef4 0010d8d4
[000092.783932] 0021bd80: 00203bf4 00015caa 00000000 00000414 00000000 80001010 00000000 00000000
[000092.792785] sp: 0021cbfc
[000092.795778] User stack:
[000092.798328] base: 0021bda0
[000092.801322] size: 00000ff0
[000092.804316] 0021cbe0: 002065d4 00000422 00000003 ffffffff 0020d488 00000002 8008e6fe 00000422
[000092.813222] 0021cc00: 00000080 00000000 00206658 8008e6fe 8008e6f8 001096f0 00000000 00000002
[000092.822111] 0021cc20: 0020d488 00000422 00000000 00000010 00000001 00000002 ffffffff 00000004
[000092.830948] 0021cc40: 00210f32 ffffffff 00000001 8010a220 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5
[000092.839952] 0021cc60: a5a5a5a5 a5a5a5a5 a5a5a5a5 00210f2c 000001b0 8008e71c 00000008 00001880
[000092.848909] 0021cc80: a5a5a5a5 a5a5a5a5 8010a220 00000422 ffffffff 0020d488 00000002 8008e742
[000092.857887] 0021cca0: ffffffff 0020d488 00000002 800981cc 389ecf69 00210d70 0020d3f8 00210f32
[000092.866860] 0021ccc0: 00210f20 00210f2d 00000000 00000000 00000000 00000000 00000000 00000000
[000092.875670] 0021cce0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.884428] 0021cd00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.893186] 0021cd20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000092.901944] 0021cd40: a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 80113eac 00210f20 800985b2
[000092.910985] 0021cd60: a5a5a5a5 0020d5fc 00202c04 800985c6 a5a5a5a5 0020d5fc 00202c04 800ff356
[000092.919994] 0021cd80: a5a5a5a5 a5a5a5a5 a5a5a5a5 00000000 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5
[000092.933368] PID PR STWM S TASK
[000092.940620] 1 3 532 X init (0x21bda0-0x21cd90, 0x21c6f8)
[000092.949592] 2 0 966 R idle (0x20fc04-0x210c00, 0x210b1c)
[000092.958557] 4 3 683 B knetd (0x21d0b0-0x21dca0, 0x21db5c)
[000092.967518] 3 5 199 B ksofttimerd (0x20f780-0x20fb70, 0x20fa9c)
[000092.976479] 7 3 303 B rt_msg (0x221600-0x221bf0, 0x221acc)
[000092.985440] 6 3 315 B knet80211d/wlan0 (0x220d30-0x221320, 0x22121c)
[000092.994401] 8 7 48 B ll (0x222b60-0x222dd0, 0x222ccc)
[000093.003357] 5 3 571 B tlsr_wlan-wlan fast taskq (0x21f710-0x220100, 0x21fffc)
断言失败
概述
断言失败对于识别故障的确切位置至关重要,这有助于在操作环境中进行调试和故障排除。
断言的目的
断言提供了一种结构化的方式来精确定位和表达特定的故障。它们作为预定义条件失败的直接指示器,有助于早期发现和修复潜在的错误。
实现断言
使用断言:
(1) 在代码中包含头文件 <assert.h>。 (2) 指定期望为真的条件。
下面提供了断言的使用示例。这种技术确保及时识别问题,避免因忽略初始错误而引发的更复杂问题。

配置断言详细程度
断言消息的详细程度可以设置为三个级别之一,允许开发者在故障处理期间选择提供的详细信息量。此设置可在构建配置过程中进行调整,如下所示:
导航到:Kernel -> General Setup -> Default
通过配置详细程度级别,开发者可以根据调试或诊断任务的需求定制断言输出。

| 断言详细程度 | 显示断言条件 | 显示函数名和行号 | 对内存占用的影响 |
|---|---|---|---|
| 2 (默认 ) | Y | Y | 高 (**) |
| 1 | Y | N | 低 |
| 0 (*) | N | N | 无 |
- (*) 将详细程度设置为0会完全禁用所有断言,通常不推荐这样做。
- (**) 这是因为可能会在可执行二进制文件中包含较长的函数名。
演示中的断言失败
通过运行演示应用程序,将展示如何在应用程序中使用断言。请参阅章节演示如何使用tlsr9118-sdk进行调试以构建和运行如何调试演示。
将有一个名为divide的CLI命令。

这是一个非常简单的CLI命令,用于执行整数除法,如下所示。

当我们传递0作为除数时,可以看到它抛出了断言失败。
$ divide 100 0
WITS 2018.02+ riscv32-elf-gcc.gnu (2022-02-07_nds32le-elf-mculib-v5-86807094a2f) 10.3.0
[000005.735229] BOARD: tlsr_w91 QFN40 EVB V1.0
[000005.735371] VFS: filesystem devfs mounted onto /dev
[000005.737228] PINCTRL: pin controller tlsr_w91,pinctrl registered
[000005.737428] GPIO: tlsr_w91,pinctrl registered as /dev/gpio
[000005.737676] atcwdt: @0xf1300000, clk=32768
[000005.737873] WDT: atcwdt registered as /dev/watchdog
[000005.738206] PINCTRL: atcspi200-xip/cs request pin 11
[000005.738391] PINCTRL: atcspi200-xip/clk request pin 12
[000005.738543] PINCTRL: atcspi200-xip/mosi request pin 13
[000005.738705] PINCTRL: atcspi200-xip/miso request pin 14
[000005.738879] PINCTRL: atcspi200-xip/hold request pin 9
[000005.739045] PINCTRL: atcspi200-xip/wp request pin 10
[000005.739537] EFUSE: efuse-tlsr_w91 registered as /dev/efuse
[000005.739664] trng version : 5e5e0010
[000005.739817] TRNG: trng registered as /dev/trng
[000005.739922] pke version : 0x5e5e0010
[000005.781866] Use fixed MAC address: 64.f9.47.f0.01.20
[000005.782856] 263 usec elapsed in downloading MAC FW.
[000007.077269] Wlan PM ctx init done
[000007.078721] UART: atcuart.0 registered as /dev/ttyS0
[000007.078854] CONSOLE: add /dev/ttyS0
[000007.079249] UART: atcuart.1 registered as /dev/ttyS1
[000007.079362] SOC: tlsr_w91
[000007.079852] Use fixed BLE public address: 01.02.03.04.05.06
[000007.082093] ble phy init 35
[000007.082216] PM feature : 0x1d
[000007.082975] PM LS : 6500+1120=7620
[000007.083125] PM SL : 6500+7100=13600
[000007.083273] PM DS : 11500+12800=24300
[000007.083429] PM HB : 1655000+60000=1715000
[000007.083677] PINCTRL: atcuart.0/txd request pin 22
[000007.083829] PINCTRL: atcuart.0/rxd request pin 21 [000336.345105] BUG: assertion(divisor != 0), check_divide_by_zero() at 36
[000336.351793] sp: 0021cc10
[000336.354780] IRQ stack:
[000336.357243] base: 0021b5b0
[000336.360236] size: 00000800
[000336.363230] User stack:
[000336.365779] base: 0021bdc0
[000336.368772] size: 00000ff0
[000336.371767] 0021cc00: 0021cc10 00203be8 00000800 8008f094 a5a5a5a5 a5a5a5a5 a5a5a5a5 0aa5a5a5
[000336.380772] 0021cc20: a5a5a5a5 a5a5a5a5 8010a2c8 00000001 0000004b 00001880 0021cdd0 0021ce04
[000336.389719] 0021cc40: 00000001 00000000 00000003 00000003 0014d210 0021bdc0 80100000 0021cdb0
[000336.398597] 0021cc60: a5a5a5a5 a5a5a5a5 8010a2c8 00000001 ffffffff 8008e7d8 8010b558 8008f0dc
[000336.407617] 0021cc80: a5a5a5a5 a5a5a5a5 00206f34 0020f1c0 ffffffff 8008e7d8 8010b558 80088df0
[000336.416653] 0021cca0: a5a5a5a5 a5a5a5a5 8010b57c 00000024 ffffffff 00000064 00000000 8008e7d8
[000336.425610] 0021ccc0: ffffffff 0020d408 00000003 80098278 ca2ca324 00210d88 0020d3f8 00210f44
[000336.434588] 0021cce0: 00210f38 00210f3f 00210f43 00000000 00000000 00000000 00000000 00000000
[000336.443430] 0021cd00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000336.452193] 0021cd20: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000336.460956] 0021cd40: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[000336.469719] 0021cd60: a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 80113f84 00210f38 8009865e
[000336.478765] 0021cd80: a5a5a5a5 0020d60c 00202c04 80098672 a5a5a5a5 0020d60c 00202c04 800ff402
[000336.487780] 0021cda0: a5a5a5a5 a5a5a5a5 a5a5a5a5 00000000 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5
[000336.498464] PID PR STWM S TASK
[000336.505721] 1 3 532 X init (0x21bdc0-0x21cdb0, 0x21c79c)
[000336.514696] 2 0 964 R idle (0x20fc1c-0x210c10, 0x210b2c)
[000336.523662] 4 3 683 B knetd (0x21d0d0-0x21dcc0, 0x21db7c)
[000336.532629] 3 5 193 B ksofttimerd (0x20f798-0x20fb90, 0x20fabc)
[000336.541595] 7 3 303 B rt_msg (0x221620-0x221c10, 0x221aec)
[000336.550561] 6 3 315 B knet80211d/wlan0 (0x220d50-0x221340, 0x22123c)
[000336.559527] 8 7 48 B ll (0x222ba0-0x222e10, 0x222d0c)
[000336.568489] 5 3 571 B tlsr_wlan-wlan fast taskq (0x21f730-0x220120, 0x22001c)
还将显示最后控制台缓冲区内容、用户堆栈内容和活动任务的信息。有关详细信息,请参阅章节核心转储。
通过分析转储的用户堆栈内容与wits.dis和wits_rom.dis(如果适用),也可以找出导致此特定断言的调用流,如章节非法指令异常所示。
堆栈溢出
概述
堆栈溢出涉及堆栈内存的意外修改,可能由于多种原因发生,例如指针处理不当、任务争用相同数据、数据结构大小不正确或错误的内存释放。此问题可能表现为多种症状,包括系统崩溃、操作挂起、堆内存泄漏、断言失败或性能下降。解决此类问题通常需要大量时间,并使用各种调试工具和技术进行战略性的方法。本文档特别关注堆栈溢出作为堆栈损坏的主要示例。
当任务或中断服务例程过度使用其分配的堆栈内存,超出其边界时,就会发生堆栈溢出。这可以等同于在操作期间忽略强制执行堆栈大小限制。
尽管其潜在严重性,堆栈溢出可能不会立即导致崩溃、挂起或断言失败,如下例所示。
堆栈溢出演示
通过运行演示应用程序,将展示应用程序中的堆栈溢出。请参阅章节演示如何使用tlsr9118-sdk进行调试以构建和运行如何调试演示。
将有一个名为print_string的CLI命令。

这是一个非常简单的CLI命令,用于将给定字符串打印到UART控制台,如下所示。

但这个演示应用程序在设计上存在重大错误。

如上所示,函数print_string从其调用者的堆栈(在本例中为init任务的堆栈)中使用了过多的空间。
结果,尽管整个系统看起来运行良好,没有任何问题,但init任务的堆栈已经被溢出。
我们可以通过两种不同的方式来验证这一点。
第一种方法是运行ps命令并检查STWM值

init任务的STWM(即堆栈水位标记)现在显示为0,这意味着在某个时刻堆栈中没有剩余空闲空间。
请注意,我们不能依赖最后记录的堆栈指针(在本例中为0x21c85c)来检测堆栈溢出。如您所见,它仍然合法地位于原始堆栈空间内,即0x21bda0和0x21cd90之间。
这是因为函数的后缀仍将返回堆栈空间并执行弹出操作。
其次,我们可以直接检查特定堆栈顶部(在本例中为0x21bda0)周围的内存区域。

可以证明堆栈已被溢出。
值得注意的是,堆栈内存在创建相应任务时将填充特定的字节模式0xa5。
通过查看完整堆栈的上边界可以说明这一点。

因此,在堆栈的上边界看到任何不同于0xa5的数据都证明发生了溢出。
在本例中,如前所述,整个系统继续运行,没有任何明显的缺陷。这使我们能够访问CLI命令以进一步调查。
但如果立即发生崩溃、挂起或断言失败,导致我们无法再访问交互式CLI,我们将需要使用其他方法继续前进,例如连接JTAG调试器以转储相关的堆栈内存。
内存泄漏
概述
TLSR9118 SDK提供了以下函数,允许用户应用程序从动态内存池(即堆)中分配和释放内存块。
| 函数(*) | 头文件 | 描述 |
|---|---|---|
| void *malloc(size_t sz) | <stdlib.h> | - 分配一个大小为sz字节的内存块并返回其起始地址。 - 如果传递的大小为0,则返回NULL。 |
| void *zalloc(size_t sz) | <stdlib.h> | - 分配一个大小为sz字节的内存块并返回其起始地址。 - 如果传递的大小为0,则返回NULL。 - 如果成功,新分配块的内容将在返回之前被清除为0。 |
| void *calloc(size_t n, size_t sz) | <stdlib.h> | - 分配一个大小为n * sz字节的内存块并返回其起始地址。 - 如果传递的大小为0,则返回NULL。 |
| void free(void *p) | <stdlib.h> | - 将先前分配的起始地址为p的内存块返回给系统堆。 - 如果p为NULL,则不执行任何操作。 |
虽然用户应用程序应小心编写,以免未能释放已使用且不再需要的动态内存,但TLSR9118 SDK提供了一个CLI命令heap来帮助确保这一点。
| CLI | 配置 | 目的 | 描述 |
|---|---|---|---|
| heap | - 命令行接口 - 了 -> 杂项实用程序 - 检 - -> heap命令 - 默认启用 |
解堆的总体使用情况 显示: 测堆内存泄漏随时间的变化 - - * |
Current 堆使用情况 Maximum 自启动以来的堆使用情况 - Free 即可用的堆空间 - Total* 堆空间大小 |
| heap check (*) | - 命令行接口 - 检 -> 杂项实用程序 -> heap命令 - 内核 -> FreeRTOS内核 -> 内存 -> 调试启用的malloc/free |
测动态分配内存块的边界是否溢出 - 检查 - 具 和 - 如 |
系统中的每个分配内存块查看是否存在边界溢出。 体检查每个分配块的头部哨兵模式,0xcafe1234, 尾部哨兵模式,0xdeaf5678。 果检查失败,显示有关损坏块的详细信息。 |
| heap list (**) | - 命令行接口 - 列 -> 杂项实用程序 - 跨 -> heap命令 - 内核 -> FreeRTOS内核 -> 内存 -> 调试启用的malloc/free - 内核 -> FreeRTOS内核 -> 内存 -> 记录内存分配函数 |
出当前从堆中分配的所有内存块 显示: 特定操作和/或随时间变化识别未回收的块。 - 所 - 分 - 块 - 块 |
- 块的索引 有者任务的名称 配块的函数的名称(如果可用) 的起始地址 的大小 |
注意
- (*)
heap check命令可能是检测动态分配堆栈溢出的另一种方法,参考章节堆栈溢出所述。 - (**) 仅当函数直接调用
malloc或其变体时,函数名称才可用。如果函数调用kmalloc或其变体,heap list将显示km-XXXXXXXX,其中0xXXXXXXXX表示跳转到kmalloc的下一条指令的地址,即返回地址,通过该地址可以推断调用者的上下文与wits.dis和/或wits_rom.dis的查找。如果内存块是从TLSR9118内部ROM中的函数分配的,则相应的条目将显示ro-XXXXXXXX,其中0xXXXXXXXX表示特定malloc调用的返回地址。
下面显示了运行这些命令的示例。
WITS 2018.02+ (Apr 21 2024 - 14:12:16 -0700)
Heap leak demo.
$
$ $ heap
Current: 31680 B
Maximum: 32272 B
Free: 270416 B
Total: 302096 B
$ $ heap check
Okay.
$
$ $ heap list
[ 0] (none) ro-8008ef02 address 21de80 size 4096
[ 1] (none) ro-8008ef10 address 21eec0 size 132
[ 2] init _if_alloc address 21ef80 size 428
[ 3] init ro-0010a484 address 21f160 size 132
[ 4] init ro-8008ef02 address 21f220 size 3072
[ 5] init ro-8008ef10 address 21fe60 size 132
[ 6] init km-0010f152 address 21ff20 size 48
[ 7] init ro-00117e48 address 21ff90 size 10
[ 8] init km-800906b6 address 21ffd0 size 24
[ 9] init km-800906b6 address 220020 size 24
[ 10] init km-0010f152 address 220070 size 48
[ 11] init ro-00117e48 address 2200e0 size 14
[ 12] init km-80093656 address 220130 size 40
[ 13] init km-800891ec address 220190 size 412
[ 14] init spi_create_defa address 220360 size 16
[ 15] init km-0010f152 address 2203b0 size 48
[ 16] init ro-00117e48 address 220420 size 11
[ 17] init km-0010f152 address 220460 size 48
[ 18] init ro-00117e48 address 2204d0 size 10
[ 19] init km-800906b6 address 220510 size 24
[ 20] init km-800891ec address 220560 size 4696
[ 21] init km-800891ec address 2217f0 size 640
[ 22] init km-0010e66c address 221ab0 size 120
[ 23] init ro-0010c052 address 221b60 size 2560
[ 24] init ro-0010c060 address 2225a0 size 124
[ 25] init _if_alloc address 222650 size 428
[ 26] init _if_alloc address 222830 size 2336
[ 27] init km-0010e66c address 223190 size 120
[ 28] init ro-0010c052 address 223240 size 1536
[ 29] init ro-0010c060 address 223880 size 124
[ 30] init ro-0011beaa address 223930 size 8
[ 31] init ro-0011beb8 address 223970 size 8
[ 32] init ro-00117d74 address 2239b0 size 480
[ 33] init ro-8008ef02 address 223bd0 size 1536
[ 34] init ro-8008ef10 address 224210 size 132
[ 35] init tlsr_wlan_init_pm address 2242d0 size 320
[ 36] init _if_alloc address 224450 size 428
[ 37] init _if_alloc address 224630 size 16
[ 38] init ro-00117d74 address 224680 size 12
[ 39] init ifa_alloc address 2246c0 size 132
[ 40] init ro-00117d74 address 224780 size 12
[ 41] init ifa_alloc address 2247c0 size 132
[ 42] init _if_alloc address 2250c0 size 428
[ 43] init _if_alloc address 2252a0 size 16
[ 44] init km-8010737a address 224880 size 252
[ 45] init km-0010f152 address 2249b0 size 48
[ 46] init km-8010737a address 224d20 size 252
[ 47] init km-0010f152 address 224e50 size 48
[ 48] init ro-00117e48 address 224ec0 size 11
[ 49] init ro-00117e48 address 224a20 size 11
[ 50] init ro-0010a484 address 224a60 size 212
[ 51] init ro-0010a484 address 224b70 size 212
[ 52] init ro-0010c052 address 2252f0 size 640
[ 53] init ro-0010c060 address 224f00 size 124
[ 54] ll km-800906b6 address 224c80 size 24
[ 55] init km-800906b6 address 224fb0 size 24
[ 56] init ro-0010a484 address 2255b0 size 340
[ 57] init ro-0010a484 address 225740 size 340
[ 58] init km-800906b6 address 225000 size 24
[ 59] init ro-001179a0 address 225050 size 16
[ 60] init ro-001179a0 address 2258d0 size 16
[ 61] init ro-001179a0 address 225920 size 16
[ 62] init ro-00117e48 address 225970 size 5
[ 63] init ro-00117e48 address 2259b0 size 11
[ 64] init ro-00117e48 address 2259f0 size 10
$
$
$ommand
演示中的内存泄漏
通过运行演示应用程序,将展示应用程序中的内存泄漏。请参阅章节演示如何使用tlsr9118-sdk进行调试以构建和运行如何调试演示,除此描述的内容外,还需要进行两项调整以构建演示应用程序。
- 选择Kernel -> FreeRTOS kernel -> Memory option -> Debug enabled malloc/free。
- 选择Kernel -> FreeRTOS kernel -> Memory option -> Record memory allocated function。
- (*) 取消选择Command line interface -> Command history。
(*) 启用命令历史记录将使用额外的堆内存,这将不必要地使讨论变得模糊。
将有一组CLI命令:save_string、purge_string和list_string。

这些命令在下表中描述,随后是运行它们的示例。
| 命令 | 描述 | 备注 |
|---|---|---|
| save_string | - 将字符串字面量保存到数组中。 - - 返回存储字符串的数组索引。 |
最多可以保存10个字符串。 - 任何多余的字符串将被丢弃。 |
| purge_string | - 从数组中删除与给定索引对应的字符串字面量并回收相关内存。 | - |
| list_string | - 列出字符串数组的内容。 | - |

演示应用程序中故意植入了一个错误,当我们尝试保存超过最大数量(在本例中为10个)的字符串时,将导致堆内存泄漏。

将展示如何使用heap CLI命令检测此错误。
首先,我们可以看到,除非达到要保存的最大字符串数量,否则堆中不会发生泄漏。

但一旦达到要保存的最大字符串数量,上述错误就会生效,如下所示。
$ list_string
$
$ heap
Current: 31568 B
Maximum: 34640 B
Free: 270480 B
Total: 302048 B
$
$
$ save_string hello
Saved hello to 0.
$ save_string hello1
Saved hello1 to 1.
$ save_string hello2
Saved hello2 to 2.
$ save_string hello3
Saved hello3 to 3.
$ save_string hello4
Saved hello4 to 4.
$ save_string hello5
Saved hello5 to 5.
$ save_string hello6
Saved hello6 to 6.
$ save_string hello7
Saved hello7 to 7.
$ save_string hello8
Saved hello8 to 8.
$ save_string hello9
Saved hello9 to 9.
$ save_string hello10
Couldn't save hello10 because there is no empty slot.
$ save_string hello11
Couldn't save hello11 because there is no empty slot.
$ save_string hello12
Couldn't save hello12 because there is no empty slot.
$ save_string hello13
Couldn't save hello13 because there is no empty slot.
$
$ list_string
[00] hello
[01] hello1
[02] hello2
[03] hello3
[04] hello4
[05] hello5
[06] hello6
[07] hello7
[08] hello8
[09] hello9
$
$ heap
Current: 32464 B
Maximum: 34640 B
Free: 269584 B
Total: 302048 B
$
$ purge_string 0
$ purge_string 1
$ purge_string 2
$ purge_string 3
$ purge_string 4
$ purge_string 5
$ purge_string 6
$ purge_string 7
$ purge_string 8
$ purge_string 9
$
$ list_string
$
$ heap
Current: 31824 B
Maximum: 34640 B
Free: 270224 B
Total: 302048 B
$
$
由于在上述示例中所有10个存储的字符串都被清除,堆的使用情况必定相同。但上述结果显示并非如此,由于上面提到的错误,未能释放4个无法存储的字符串。
比较堆中的可用空间可以表明存在一些内存泄漏,如上所示。但它并没有提供任何进一步的细节。
要列出系统中所有未回收的内存块将帮助我们理解发生了什么。heap list命令为此发挥作用。
$ heap list
[ 0] (none) ro-8008f122 address 21deb0 size 4096
[ 1] (none) ro-8008f130 address 21eef0 size 132
[ 2] init _if_alloc address 21efb0 size 428
[ 3] init ro-0010a484 address 21f190 size 132
[ 4] init ro-8008f122 address 21f250 size 3072
[ 5] init ro-8008f130 address 21fe90 size 132
[ 6] init km-0010f152 address 21ff50 size 48
[ 7] init ro-00117e48 address 21ffc0 size 10
[ 8] init km-800908d0 address 220000 size 24
[ 9] init km-800908d0 address 220050 size 24
[ 10] init km-0010f152 address 2200a0 size 48
[ 11] init ro-00117e48 address 220110 size 14
[ 12] init km-80093c10 address 220160 size 40
[ 13] init km-80089214 address 2201c0 size 412
[ 14] init spi_create_defa address 220390 size 16
[ 15] init km-0010f152 address 2203e0 size 48
[ 16] init ro-00117e48 address 220450 size 11
[ 17] init km-0010f152 address 220490 size 48
[ 18] init ro-00117e48 address 220500 size 10
[ 19] init km-800908d0 address 220540 size 24
[ 20] init km-80089214 address 220590 size 4696
[ 21] init km-80089214 address 221820 size 640
[ 22] init km-0010e66c address 221ae0 size 120
[ 23] init ro-0010c052 address 221b90 size 2560
[ 24] init ro-0010c060 address 2225d0 size 124
[ 25] init _if_alloc address 222680 size 428
[ 26] init _if_alloc address 222860 size 2336
[ 27] init km-0010e66c address 2231c0 size 120
[ 28] init ro-0010c052 address 223270 size 1536
[ 29] init ro-0010c060 address 2238b0 size 124
[ 30] init ro-0011beaa address 223960 size 8
[ 31] init ro-0011beb8 address 2239a0 size 8
[ 32] init ro-00117d74 address 2239e0 size 480
[ 33] init ro-8008f122 address 223c00 size 1536
[ 34] init ro-8008f130 address 224240 size 132
[ 35] init tlsr_wlan_init_pm address 224300 size 320
[ 36] init _if_alloc address 224480 size 428
[ 37] init _if_alloc address 224660 size 16
[ 38] init ro-00117d74 address 2246b0 size 12
[ 39] init ifa_alloc address 2246f0 size 132
[ 40] init km-801079f4 address 2247b0 size 252
[ 41] init km-0010f152 address 2248e0 size 48
[ 42] init _if_alloc address 2250f0 size 428
[ 43] init _if_alloc address 2252d0 size 16
[ 44] init ro-00117d74 address 225320 size 12
[ 45] init ifa_alloc address 225360 size 132
[ 46] init ro-00117e48 address 224950 size 11
[ 47] init ro-0010a484 address 224990 size 212
[ 48] init km-801079f4 address 225d60 size 252
[ 49] init km-0010f152 address 225e90 size 48
[ 50] init ro-00117e48 address 225f00 size 11
[ 51] init ro-0010a484 address 224aa0 size 212
[ 52] init ro-0010c052 address 224bb0 size 640
[ 53] init ro-0010c060 address 224e70 size 124
[ 54] ll km-800908d0 address 224f20 size 24
[ 55] init km-800908d0 address 224f70 size 24
[ 56] init ro-0010a484 address 225420 size 340
[ 57] init ro-0010a484 address 2255b0 size 340
[ 58] init km-800908d0 address 224fc0 size 24
[ 59] init ro-001179a0 address 225010 size 16
[ 60] init ro-001179a0 address 225060 size 16
[ 61] init ro-001179a0 address 225740 size 16
[ 62] init save_string address 225a10 size 8
[ 63] init save_string address 225a50 size 8
[ 64] init save_string address 225a90 size 8
[ 65] init save_string address 225ad0 size 8
$
发现有4个内存块未回收,而它们本应被回收。
虽然这个例子可能看起来过于人为,但使用heap和heap list命令将极大地帮助解决更困难的内存泄漏问题。
客户示例说明
概述
本章节介绍了客户示例模块,在 customer_demo 目录下,我们提供了各模块的实际使用参考代码,方便用户根据自身需求进行修改和二次开发。
除了前面章节介绍的 CLI 指令验证方式外,这些示例代码展示了如何调用 SDK 接口实现具体功能。每个示例的详细操作步骤、注意事项和说明文档,请参阅 SDK 示例文件夹下对应的 .md 文件。
客户示例列表
adc_demo
演示如何使用芯片内部的 ADC 模块,将模拟电压信号转换为数字值。
常见应用场景:
- 电池电压检测:读取电压以计算剩余电量。
- 传感器读取:读取光敏电阻、热敏电阻等模拟输出传感器的数据。
- 模拟音频输入:简单的麦克风信号采集。
gpio_demo
演示如何配置 GPIO 引脚为输出模式,并控制其输出高、低电平。
常见应用场景:
- LED 控制:控制 LED 灯的闪烁(Blink)。
- 流控引脚:通过 GPIO 电平控制外设接口的通信流控。
i2c_demo
演示如何将 GPIO 配置为 I2C 接口(SCL 和 SDA),并实现主模式下的数据读写时序。
常见应用场景:
- 存储器读写:与外部 EEPROM(如 AT24C02)通信。
- 传感器通信:读取温湿度传感器(如 SHT30)、加速度计(如 MPU6050)的数据。
- 外设控制:控制触摸屏芯片或 I/O 扩展芯片。
spi_demo
演示如何使用 SPI 接口进行高速同步数据传输,包括时钟极性、相位和速率的配置。
常见应用场景:
- 显示驱动:驱动 TFT LCD 或 OLED 屏幕(适合刷屏等高速场景)。
- 外部存储:读写外部 NOR Flash 或 NAND Flash。
- 高速通信:与另一 MCU 进行大量数据交换。
uart_demo
演示如何配置波特率、数据位、停止位,并进行字符/字符串的发送与接收(支持轮询和中断模式)。
常见应用场景:
- 调试日志:实现
printf功能,将调试信息输出到终端。 - 命令交互:实现 CLI(命令行接口),通过串口指令控制设备。
- 模块通信:与 GPS 模块、指纹模块或无线模组通信。
pwm_demo
演示如何配置定时器,在指定引脚输出频率和占空比可调的 PWM 波形。
常见应用场景:
- 灯光控制:调节 LED 亮度或实现呼吸灯效果。
- 电机控制:控制直流电机转速或伺服舵机角度。
- 音频输出:驱动无源蜂鸣器播放不同音调。
bledistr_demo
演示如何使用蓝牙模块进行获取AP的SSID和PASSWORD信息进行网络连接。
常见应用场景:
- IOT产品 or AI智能产品等:获取AP的信息以实现成功接入。
sapdistr_demo
演示芯片作为softAP端接收STA发送过来的AP的SSID和PASSWORD信息进行网络连接。
常见应用场景:
- IOT产品 or AI智能产品等:获取AP的信息以实现成功接入。
audio_demo
演示如何通过I2C配置Codec模块,以及通过I2S进行音频数据的传输。同时本实例提供了基于HTTP/MQTT的音频数据传输的使用示例,并提供了基于音频硬件控制和音频数据管控的简易状态机。
常见应用场景:
- 通过HTTP联网获取MP3音频文件,并在本地进行播放。
- 音频数据的播放和采集场景。
- 通过MQTT联网进行音频数据的下载与上传场景。
pm_uart_demo
演示在低功耗模式下使用 UART 功能。通过 PM 接口和 UART 任务控制,确保芯片在休眠唤醒后 UART 功能可连续使用。通常要求对端 UART 设备在发送数据前通过 GPIO 进行唤醒。
常见应用场景:
- 低功耗产品:适用于电池供电或无源设备,需要在工作与低功耗状态间切换的应用。
http_client_demo
演示了接入指定http服务器端后get data和post data的功能。
常见应用场景:
- IOT产品 or AI智能产品等:应用于指令控制或者文件获取或关键信息获取等。
mqtt_client_demo
演示了接入指定的mqtt服务器断后publish data和subscribe data的功能。
常见应用场景:
- IOT产品 or AI智能产品等:应用于指令控制或者文件获取或关键信息获取等。