LiteOS与SLE透传实战案例
文章目录
- 硬件设计
- EDA 软件介绍
- 创建元件及封装
- 原理图绘制
- Layout
- 元件焊接
- 软件设计
- LiteOS 入门
- 核心概念
- Task
- Workflow
- 参考 API(参考 osal_task. h)
- 时间片
- 任务轮转
- 练习:仿写 example/peripheral/blinky
- Queue
- 参考 API(参考 osal_msgqueue. h)
- 错误处理 (watch dog)
- Example
- 通过 Queue 点亮 LED
- 通过 Queue 实现流式传输
- SLE 透传 demo
- Workflow
- Code
- sle_uart. c
- Server
- sle_uart_server. c
- sle_uart_server_adv. c
- Client
- sle_uart_client. c
- Solution
硬件设计
设计一款支持星闪(NearLink)技术的无线透传设备,核心模块选用 BM-H63 模块,其特性包括:
- 支持星闪协议
- 通过 USB-A 接口与电脑连接,即插即用
基于深圳大学 AutoLeaders 俱乐部开源项目“WS63星闪dongle”进行硬件设计
项目链接:WS63星闪dongle
原理图:
EDA 软件介绍
软件功能详解:
- 原理图设计:符号库调用、电气规则检查(ERC)
- PCB 布局:层叠结构定义(本设计采用双层板)、布线规则设置
- 元件库管理:
- 基础元件:电阻(限流/分压)、电感(滤波)、电容(去耦)
- 芯片类:CH340G(引脚功能:TXD/RXD 串口通信)、AMS1117(输入/输出/接地引脚配置)
- 团队协作功能:支持多人实时编辑
创建元件及封装
BM-H63 自定义封装设计流程:
-
引脚定义提取
- 根据模块数据手册标注引脚(如 VCC、GND、UART_TX/RX)
- 数据手册链接:BM-H63 数据手册
- 模块尺寸图
-
焊盘设计:
- 尺寸匹配
- 间距控制
-
符号库绑定:原理图符号与封装绑定,添加引脚电气属性注释
原理图绘制
- 芯片数据手册阅读
- CH340G:重点关注 USB 差分信号(D+/D-)处理
- 参考资料链接:CH340G 数据手册
- AMS1117:输入/输出电压稳定设计,需添加去耦电容
2. 参考资料链接:AMS1117 数据手册
- CH340G:重点关注 USB 差分信号(D+/D-)处理
- 模块绘制:
- 供电模块:5V 转 3.3V 电路
- 通信模块:BM-H63 与 CH340 的 UART 连接
- 标注规范:
- 网络标签命名规则(如 VCC_3V3、UART1_TX)
- 添加设计注释
Layout
- 布局策略:
- 模块分区
- 布线设计:避免直角转弯
- 铺铜处理:
- 顶层/底层整板覆铜(GND 网络),添加缝合孔
- 设计验证:
2. DRC 检查
3. 输出 Gerber 文件 - PCB 下单
元件焊接
- 焊接安全注意事项
- 焊接操作注意事项
- 焊接质量检测
- 焊点光泽度
- 引脚无桥接、虚焊
- 万用表导通测试
软件设计
环境配置:ws63 开发环境配置 - Gitee
LiteOS 入门
参考资料:Huawei LiteOS - 华为云
核心概念
Task
从系统角度看,任务是竞争系统资源的最小运行单元。任务可以使用或等待 CPU、使用内存空间等系统资源,并独立于其它任务运行。
LiteOS 的任务模块支持多任务切换,帮助用户管理程序流程。Huawei LiteOS的任务模块具有如下特性:
- 支持多任务
- 一个任务表示一个线程
- 抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度
- 相同优先级任务支持时间片轮转调度方式
任务状态
Huawei LiteOS系统中的任务有多种运行状态。系统初始化完成后,创建的任务就可以在系统中竞争一定的资源,由内核进行调度
任务状态通常分为以下四种:
- 就绪(Ready):该任务在就绪队列中,只等待CPU
- 运行(Running):该任务正在执行
- 阻塞(Blocked):该任务不在就绪队列中。包含任务被挂起(suspend状态)、任务被延时(delay状态)、任务正在等待信号量、读写队列或者等待事件等
- 退出态(Dead):该任务运行结束,等待系统回收资源
任务ID
任务ID,在任务创建时通过参数返回给用户,是任务的重要标识。系统中的ID号是唯一的。用户可以通过任务ID对指定任务进行任务挂起、任务恢复、查询任务名等操作
任务优先级
优先级表示任务执行的优先顺序。任务的优先级决定了在发生任务切换时即将要执行的任务,就绪队列中最高优先级的任务将得到执行
任务入口函数
新任务得到调度后将执行的函数。该函数由用户实现,在任务创建时,通过任务创建结构体设置
任务栈
每个任务都拥有一个独立的栈空间,称为任务栈。栈空间里保存的信息包含局部变量、寄存器、函数参数、函数返回地址等
任务上下文
任务在运行过程中使用的一些资源,如寄存器等,称为任务上下文。当这个任务挂起时,其他任务继续执行,可能会修改寄存器等资源中的值。如果任务切换时没有保存任务上下文,可能会导致任务恢复后出现未知错误
因此,Huawei LiteOS在任务切换时会将切出任务的任务上下文信息,保存在自身的任务栈中,以便任务恢复后,从栈空间中恢复挂起时的上下文信息,从而继续执行挂起时被打断的代码。
任务切换
任务切换包含获取就绪队列中最高优先级任务、切出任务上下文保存、切入任务上下文恢复等动作
Workflow
- 创建任务句柄
- 锁定调度
- 创建任务
- 设置任务优先级
- 释放任务句柄
- 解锁调度
示例代码:
// 来自 blinky_demo.c
static void blinky_entry(void)
{ osal_task *task_handle = NULL; // 创建任务句柄 osal_kthread_lock(); // 锁定任务调度 task_handle = osal_kthread_create((osal_kthread_handler)blinky_task, 0, "BlinkyTask", BLINKY_TASK_STACK_SIZE); // 创建任务 if (task_handle != NULL) // 检查任务是否创建成功 { osal_kthread_set_priority(task_handle, BLINKY_TASK_PRIO); // 设置任务优先级 osal_kfree(task_handle); // 释放任务句柄 } osal_kthread_unlock(); // 解锁任务调度
}
参考 API(参考 osal_task. h)
文件路径 src\kernel\osal\include\schedule
osal_kthread_create ()
提供一个接口用于创建线程,并调用 kthread_run
来创建内核线程
- 如果创建的任务堆栈大小小于或等于
MINIMAL_STACK_SIZE
,则将stack_size
设置为MINIMAL_STACK_SIZE
,以指定任务堆栈的默认大小 - 堆栈大小由是否足够避免任务堆栈溢出来决定
/*** @ingroup osal_task** @brief 提供一个接口用于创建线程,并调用 `kthread_run` 来创建内核线程。** @param stack_size [in] 线程堆栈空间的大小。* @param handler [in] 线程要处理的函数。* @param data [in] 传递给函数处理的数据。* @param name [in] 线程显示的名称。** @retval osal_task* 如果线程创建成功,返回线程的指针。* @retval NULL 如果线程创建失败,返回 NULL。** @par 支持的系统:* Linux、LiteOS、FreeRTOS。*/
osal_task *osal_kthread_create(osal_kthread_handler handler, void *data, const char *name, unsigned int stack_size);
osal_kthread_lock
用于锁定任务调度。如果任务调度被锁定,则不会发生任务切换
- 每次调用此 API,任务调度锁的计数会加 1。如果任务调度被解锁,计数会减 1
osal_kthread_unlock
用于解锁任务调度。调用此 API 会将任务锁的计数减 1
- 如果任务被多次锁定,只有当锁的计数变为 0 时,任务调度才会被解锁
/*** @ingroup osal_task* @brief 锁定任务调度。** @attention* 如果任务调度被锁定,但中断未被禁用,任务仍然可以被中断。* 每次调用此 API,任务调度锁的计数会加 1。如果任务调度被解锁,计数会减 1。* 因此,此 API 应与 `osal_kthread_unlock` 一起使用。** @par 支持的系统:* LiteOS、FreeRTOS。*/
void osal_kthread_lock(void);// ------/*** @ingroup osal_task* @brief 解锁任务调度。** @attention* 每次调用此 API,任务调度锁的计数会减 1。如果任务调度被锁定,计数会增加 1。* 因此,此 API 应与 `osal_kthread_lock` 一起使用。** @par 支持的系统:* LiteOS、FreeRTOS。*/
void osal_kthread_unlock(void);
osal_kthread_set_priority ()
设置线程的优先级
/*** @ingroup osal_task** @brief 设置线程的优先级。** @param task [in] 要设置优先级的线程。* @param priority [in] 要设置的优先级,必须是以下三个优先级之一:* OSAL_TASK_PRIORITY_HIGH、OSAL_TASK_PRIORITY_MIDDLE、OSAL_TASK_PRIORITY_LOW。** @return OSAL_FAILURE 或 OSAL_SUCCESS。** @par 支持的系统:* Linux、LiteOS、FreeRTOS。*/
int osal_kthread_set_priority(osal_task *task, unsigned int priority);
osal_msleep ()
/*** @ingroup osal_task* @brief 休眠。** @par 描述:* 使当前线程休眠指定的时间。** @param msecs [in] 休眠的时间(以毫秒为单位)。** @return 如果定时器到期,则返回 0;否则返回剩余的时间(以毫秒为单位)。** @par 支持的系统:* Linux、LiteOS、FreeRTOS。*/
unsigned long osal_msleep(unsigned int msecs);
[!warning] 使用 delay 函数会触发 watchdog, 原因暂时不明,建议使用 sleep
时间片
时间片(Time slice 或 Time quantum)是抢占式多任务操作系统中的一个概念,指的是操作系统分配给每个进程或任务的固定执行时间
每个任务在执行时间达到时间片的长度后,操作系统会进行任务切换,将 CPU 的控制权交给下一个任务
LiteOS 通过将很短的一段时间分给多个任务来达到“同时运行多个任务”的效果,实际上在某一各瞬间,运行的还是一个任务(对于单核心 MCU)
LiteOS 使用抢占式调度,高优先级任务会抢占低优先级任务的执行,从而满足实时性要求
任务轮转
LiteOS 中,任务通过优先级来进行调度,高优先级任务会抢占低优先级任务的执行
任务轮转是指在多任务并发执行的系统中,操作系统会按照任务的优先级和时间片来决定任务何时被执行
当一个任务的时间片用完或者发生任务切换条件时,调度器会选择下一个任务进行执行,从而实现多任务并行
练习:仿写 example/peripheral/blinky
引脚连接方式参考:BM-H63 数据手册
引脚模式参考:ws63 引脚模式定义
set(SOURCES_LIST
${CMAKE_CURRENT_SOURCE_DIR}/myBlinky.c
) set(PUBLIC_HEADER_LIST
${CMAKE_CURRENT_SOURCE_DIR}
) set(SOURCES "${SOURCES_LIST}" PARENT_SCOPE)
set(PUBLIC_HEADER "${PUBLIC_HEADER_LIST}" PARENT_SCOPE)
代码来源:
/** * Copyright (c) HiSilicon (Shanghai) Technologies Co., Ltd. 2023-2023. All rights reserved. * * Description: Blinky Sample Source. \n * * History: \n * 2023-04-03, Create file. \n */ #include "pinctrl.h"
#include "gpio.h"
#include "soc_osal.h"
#include "app_init.h" #define BLINKY_DURATION_MS 500 #define BLINKY_TASK_PRIO 24
#define BLINKY_TASK_STACK_SIZE 0x1000 static int blinky_task(const char *arg)
{ unused(arg); uapi_pin_set_mode(CONFIG_BLINKY_PIN, HAL_PIO_FUNC_GPIO); uapi_gpio_set_dir(CONFIG_BLINKY_PIN, GPIO_DIRECTION_OUTPUT); uapi_gpio_set_val(CONFIG_BLINKY_PIN, GPIO_LEVEL_LOW); while (1) { osal_msleep(BLINKY_DURATION_MS); uapi_gpio_toggle(CONFIG_BLINKY_PIN); osal_printk("Blinky working.\r\n"); } return 0;
} static void blinky_entry(void)
{ osal_task *task_handle = NULL; osal_kthread_lock(); task_handle = osal_kthread_create((osal_kthread_handler)blinky_task, 0, "BlinkyTask", BLINKY_TASK_STACK_SIZE); if (task_handle != NULL) { osal_kthread_set_priority(task_handle, BLINKY_TASK_PRIO); osal_kfree(task_handle); } osal_kthread_unlock();
} /* Run the blinky_entry. */
app_run(blinky_entry);
Queue
队列又称消息队列,是一种常用于任务间通信的数据结构
队列接收来自任务或中断的不固定长度消息,并根据不同的接口确定传递的消息是否存放在队列空间中
- 任务能够从队列里面读取消息,当队列中的消息为空时,挂起读取任务;当队列中有新消息时,挂起的读取任务被唤醒并处理新消息
- 任务也能够往队列里写入消息,当队列已经写满消息时,挂起写入任务;当队列中有空闲消息节点时,挂起的写入任务被唤醒并写入消息。如果将读队列和写队列的超时时间设置为0,则不会挂起任务,接口会直接返回,这就是非阻塞模式
消息队列提供了异步处理机制,允许将一个消息放入队列,但不立即处理。同时队列还有缓冲消息的作用
Huawei LiteOS中使用队列实现任务异步通信,具有如下特性:
- 消息以先进先出的方式排队,支持异步读写
- 读队列和写队列都支持超时机制
- 每读取一条消息,就会将该消息节点设置为空闲
- 发送消息类型由通信双方约定,可以允许不同长度(不超过队列的消息节点大小)的消息
- 一个任务能够从任意一个消息队列接收和发送消息
- 多个任务能够从同一个消息队列接收和发送消息
- 创建队列时所需的队列空间,默认支持接口内系统自行动态申请内存的方式,同时也支持将用户分配的队列空间作为接口入参传入的方式
如果使用全局变量,当两个 Task 同时访问、更改同一个变量时,可能会出现不可预知的错误
参考 API(参考 osal_msgqueue. h)
文件路径 src\kernel\osal\include\queue
osal_msg_queue_create ()
创建队列
/*** @ingroup osal_msgqueue* @brief 创建消息队列。** @par 描述:* 此 API 用于创建一个消息队列。** @attention* 系统中可用的队列数量由 `LOSCFG_BASE_IPC_QUEUE_LIMIT` 决定,必要时可以修改其值。* 此函数仅在定义了 `LOSCFG_QUEUE_DYNAMIC_ALLOCATION` 时有效。* 在 FreeRTOS 系统中,输入参数 `queue_id` 被用作地址;在 LiteOS 系统中,`queue_id` 被用作整数。* 参数 `queue_id` 是通过 `osal_msg_queue_create` 创建的。** @param name [in] 消息队列名称。保留参数,目前未使用。* @param queue_len [in] 队列长度。取值范围为 [1, 0xffff]。* @param queue_id [out] 成功创建的队列控制结构的 ID。* @param flags [in] 队列模式。保留参数,目前未使用。* @param max_msgsize [in] 节点大小。取值范围为 [1, 0xffff]。** @return OSAL_SUCCESS/OSAL_FAILURE** @par 支持的系统:* LiteOS、FreeRTOS。*/
int osal_msg_queue_create(const char *name, unsigned short queue_len, unsigned long *queue_id, unsigned int flags,unsigned short max_msgsize);
osal_msg_queue_write_copy ()
将数据写入消息队列
/*** @ingroup osal_msgqueue* @brief 将数据写入消息队列。** @par 描述:* 此 API 用于将指定大小的数据(由 `buffer_size` 指定)从指定地址(由 `buffer_addr` 指定)写入消息队列。** @attention* - 必须先创建消息队列。* - 不要在非阻塞模式(如中断)中读写队列。* - 在 LiteOS 初始化之前不能调用此 API。* - 要写入的数据大小由 `buffer_size` 指定,数据存储在 `buffer_addr` 指定的地址中。* - 参数 `timeout` 是相对时间。* - 不要在软件定时器回调中调用此 API。* - 在 FreeRTOS 系统中,`buffer_size` 参数未使用,队列长度取决于创建时传递的大小。* - 在 FreeRTOS 系统中,`queue_id` 被用作地址;在 LiteOS 系统中,`queue_id` 被用作整数。* - 参数 `queue_id` 是通过 `osal_msg_queue_create` 创建的。** @param queue_id [in] 消息队列的 ID,由 `osal_msg_queue_create` 创建。* @param buffer_addr [in] 存储要写入数据的起始地址。起始地址不能为空。* @param buffer_size [in] 要写入的缓冲区大小。* @param timeout [in] 超时时间(单位:Tick)。** @return OSAL_SUCCESS/OSAL_FAILURE** @par 支持的系统:* LiteOS、FreeRTOS* @par 系统差异:* 在 FreeRTOS 中,`buffer_size` 不支持指定大小的读写,仅支持完整存储和循环操作。*/
int osal_msg_queue_write_copy(unsigned long queue_id, void *buffer_addr, unsigned int buffer_size,unsigned int timeout);
超时等待,在 osal_wait.h
中定义
OSAL_WAIT_FOREVER 永久等待
osal_msg_queue_read_copy ()
读取消息队列
这里的 buffer_size 参数需要的是 unsigned int *
/*** @ingroup osal_msgqueue* @brief 读取消息队列。** @par 描述:* 此 API 用于从指定的消息队列中读取数据,并将获取到的数据存储到 `buffer_addr` 指定的地址中。* 数据的地址和大小由用户定义。** @attention* - 必须先创建消息队列。* - 消息队列的读取采用先进先出(FIFO)模式,最先存储的数据会被最先读取。* - `buffer_addr` 用于存储获取到的数据。* - 不要在非阻塞模式(如中断)中读写队列。* - 在 LiteOS 初始化之前不能调用此 API。* - 参数 `timeout` 是相对时间。* - 不要在软件定时器回调中调用此 API。* - 在 FreeRTOS 系统中,`buffer_size` 参数未使用,队列长度取决于创建时传递的大小。* - 在 FreeRTOS 系统中,`queue_id` 被用作地址;在 LiteOS 系统中,`queue_id` 被用作整数。* - 参数 `queue_id` 是通过 `osal_msg_queue_create` 创建的。** @param queue_id [in] 消息队列的 ID,由 `osal_msg_queue_create` 创建。* @param buffer_addr [out] 存储读取数据的起始地址。起始地址不能为空。* @param buffer_size [in/out] 在读取之前表示期望的缓冲区大小,读取之后表示实际读取的大小。* @param timeout [in] 超时时间(单位:Tick)。** @return OSAL_SUCCESS/OSAL_FAILURE** @par 支持的系统:* LiteOS、FreeRTOS* @par 系统差异:* 在 FreeRTOS 中,`buffer_size` 不支持指定大小的读写,仅支持完整存储和循环操作。*/
int osal_msg_queue_read_copy(unsigned long queue_id, void *buffer_addr, unsigned int *buffer_size,unsigned int timeout);
错误处理 (watch dog)
基本概念
错误处理指程序运行错误时,调用错误处理模块的接口函数,上报错误信息,并调用注册的钩子函数进行特定处理,保存现场以便定位问题
通过错误处理,可以控制和提示程序中的非法输入,防止程序崩溃
运作机制
错误处理是一种机制,用于处理异常状况。当程序出现错误时,会显示相应的错误码。此外,如果注册了相应的错误处理函数,则会执行这个函数
Example
通过 Queue 点亮 LED
实验原理:通过队列在两个任务间传输自增变量,根据变量的奇偶控制 LED 的点亮和熄灭,观察 Queue 在 Task 间的数据传输作用
关键点:
- Task 创建
- sendMsgTask:负责发送数据
- recvMsgTask:负责接收数据并判断电量、熄灭 LED
- Queue 创建
- 创建一个 Queue 用于数据写入、读取
- GPIO 控制
实验目的:学习并理解 Queue 在任务间数据传输中的作用并在工程中进行运用
任务点:移植点灯代码
实验代码:
#include "soc_osal.h" // 包含 liteos 的头文件
#include "app_init.h" // 程序入口函数头文件
#include "gpio.h" // 包含 gpio 的头文件
#include "pinctrl.h" // 包含 pinctrl 的头文件
#include "osal_wait.h" #define BLINKY_PIN 2 // 定义引脚号
#define QUEUE_NAME "myQueue" // 定义队列名
#define QUEUE_SIZE 12 // 定义队列大小
#define QUEUE_NODE_SIZE 4 // 定义队列节点大小,单位字节
#define MSESSAGE_TASK_STACK_SIZE 4 * 1024 // 定义任务栈大小 unsigned long queueID = 0; void sendMsgTask(void)
{ osal_printk("sendMsgTask start\r\n"); uint32_t sendMsg = 0; uint32_t ret = 0; while (1) { ret = osal_msg_queue_write_copy(queueID, &sendMsg, sizeof(sendMsg), 0xff); if (ret == OSAL_SUCCESS) { osal_printk("send message success, sendMsg = %d\n", sendMsg); } else { osal_printk("send message failed, sendMsg = %d\n", sendMsg); } sendMsg++; osal_msleep(1000); }
} void recvMsgTask(void)
{ osal_printk("recvMsgTask start\r\n"); uint32_t recvMsg = 666; uint32_t ret = 0; uint32_t msgSize = 4; // 定义缓冲区大小,单位字节 while (1) { ret = osal_msg_queue_read_copy(queueID, &recvMsg, &msgSize, OSAL_WAIT_FOREVER); // 注意这里的 msgSize 一定要是指针 if (ret == OSAL_SUCCESS) { osal_printk("recv message success, recvMsg = %d\n", recvMsg); if (recvMsg % 2 == 0) { uapi_gpio_set_val(BLINKY_PIN, GPIO_LEVEL_HIGH); } else { uapi_gpio_set_val(BLINKY_PIN, GPIO_LEVEL_LOW); } } else { osal_printk("recv message failed, recvMsg = %d\n", recvMsg); } osal_msleep(1000); }
} void blinkyInit(void)
{ uapi_pin_set_mode(BLINKY_PIN, HAL_PIO_FUNC_GPIO); // 设置引脚模式为 GPIO uapi_gpio_set_dir(BLINKY_PIN, GPIO_DIRECTION_OUTPUT); // 设置引脚方向为输出 uapi_gpio_set_val(BLINKY_PIN, GPIO_LEVEL_LOW); // 设置引脚输出低电平
} void sysInit(void)
{ blinkyInit(); uint32_t ret = 0; ret = osal_msg_queue_create(QUEUE_NAME, QUEUE_SIZE, &queueID, 0, QUEUE_NODE_SIZE); if (ret != OSAL_SUCCESS) { osal_printk("create queue failed, ret = %d\n", ret); } else { osal_printk("create queue success, queueID = %d\n", queueID); } osal_task *sendTaskHandle = NULL; // 创建任务句柄 osal_task *recvTaskHandle = NULL; // 创建任务句柄 osal_kthread_lock(); // 锁定任务调度 sendTaskHandle = osal_kthread_create((osal_kthread_handler)sendMsgTask, NULL, "SendTask", MSESSAGE_TASK_STACK_SIZE); // 创建任务 if (sendTaskHandle != NULL) // 检查任务是否创建成功 { osal_kthread_set_priority(sendTaskHandle, OSAL_TASK_PRIORITY_MIDDLE); // 设置任务优先级 osal_kfree(sendTaskHandle); // 释放任务句柄 } recvTaskHandle = osal_kthread_create((osal_kthread_handler)recvMsgTask, NULL, "RecvTask", MSESSAGE_TASK_STACK_SIZE); // 创建任务 if (recvTaskHandle != NULL) // 检查任务是否创建成功 { osal_kthread_set_priority(recvTaskHandle, OSAL_TASK_PRIORITY_MIDDLE); // 设置任务优先级 osal_kfree(recvTaskHandle); // 释放任务句柄 } osal_kthread_unlock(); // 解锁任务调度
} app_run(sysInit);
通过 Queue 实现流式传输
流数据(Stream Data)是指持续不断产生和传输的数据
这些数据通常是按时间顺序排列,并实时传输和处理, 与传统的批量数据处理不同,流数据需要在数据生成的同时进行即时处理,如实时传感器数据、社交媒体的消息流、金融交易数据、网络流量等
此类数据的数据量通常较大,若 Queue 大小过大,则会造成资源浪费,因此使用流式传输的传输数据,将数据分段发出
实验原理:将获取到的数据逐字符发出,以实现,流式传输的效果
关键点:
- Task 创建
- Queue 创建:逐字符传输数据,在队列满时进行判断
- 通过 memset、memmove 等函数对内存进行操作
实验目的:学习并理解流式传输的原理并通过 Queue 实现流式传输
内存操作函数:
需包含 <string. h>
/*** @brief 用指定值填充内存区域。** @par 描述:* 此 API 用于将指定值填充到目标内存区域的前 num 个字节。** @attention* - 目标内存区域必须可写且有效* - 填充长度 num 不应超过目标内存区域的实际容量* - 当目标指针为 NULL 时行为未定义(取决于具体系统实现)** @param ptr 指向要填充的内存区域的指针* @param value 要设置的值(以 unsigned char 形式使用)* @param num 要填充的字节数,取值范围为 [0, SIZE_MAX]**/
void *memset(void *ptr, int value, size_t num);/*** @ingroup osal_memory* @brief 安全复制内存块(支持重叠区域)。** @par 描述:* 此 API 用于从源内存区域复制 num 个字节到目标内存区域,能正确处理源和目标内存重叠的情况。** @attention* - 源和目标内存区域必须有效且可访问* - 复制长度 num 为 0 时函数无实际操作* - 当 src/dest 为 NULL 时行为未定义* - 不推荐用于设备内存映射区域(需使用内存屏障操作)** @param dest 目标内存区域指针* @param src 源内存区域指针* @param num 要复制的字节数,取值范围为 [0, SIZE_MAX]**/
void *memmove(void *dest, const void *src, size_t num);
实验代码:
/** * @file streamTest.c * @author lamonce * @brief Simulate stream transmission using a queue based on LiteOS. * @version 1.0 * @date 2025-03-24 * */ #include "soc_osal.h" // 包含 liteos 的头文件
#include "app_init.h" // 程序入口函数头文件
#include "gpio.h" // 包含 gpio 的头文件
#include "pinctrl.h" // 包含 pinctrl 的头文件
#include "osal_wait.h"
#include "string/osal_string.h"
#include <string.h> #define QUEUE_NAME "streamQueue" // 定义队列名
#define QUEUE_SIZE 50 // 定义队列大小
#define QUEUE_NODE_SIZE 1 // 定义队列节点大小,单位字节
#define MSESSAGE_TASK_STACK_SIZE 4 * 1024 // 定义任务栈大小
#define BLINKY_PIN 2 // 定义引脚号 unsigned long queueID = 0; /*** @brief 将字符串从 sourceString 复制到 destString,若 sourceString 长度大于 size,则只复制 size 个字符,sourceString 中被复制的字符会被清空 * * @param sourceString 源字符串 * @param destString 目标字符串 * @param sourceLength 源字符串长度,通过 strlen() 获取 * @param destLength 目标字符串长度,通过 sizeof() 获取 * @return int 当 sourceLength 大于 destLength 时返回 1,否则返回 0 */
int stringCopy(char *sourceString, char *destString, size_t sourceLength, size_t destLength)
{ if (sourceLength > destLength) { strncpy(destString, sourceString, destLength - 1); destString[destLength - 1] = '\0'; memmove(sourceString, sourceString + (destLength - 1), sourceLength - destLength + 2); memset(sourceString + (sourceLength - destLength + 1), 0, destLength - 1); return 1; } else { strcpy(destString, sourceString); memset(sourceString, 0, sourceLength); return 0; }
} void sendMsgTask(void)
{ osal_printk("sendMsgTask start\r\n"); uint32_t ret = 0; uint32_t index = 0; char sendMsg[QUEUE_SIZE] = {0}; char MSG[80] = {"0123456789012345678901234567890123456789012345678901234567890123456789012345678"}; // 创建用于测试的字符串,长度长于 QUEUE_SIZE char tempMsg[80] = {"0123456789012345678901234567890123456789012345678901234567890123456789012345678"}; while (true) { stringCopy(tempMsg, sendMsg, strlen(tempMsg), QUEUE_SIZE); for (int i = 0; i < QUEUE_SIZE; i++) { if (sendMsg[i] == '\0') { osal_printk("msg is empty\n"); break; } osal_printk("%c", sendMsg[i]); ret = osal_msg_queue_write_copy(queueID, &sendMsg[i], sizeof(sendMsg[i]), 0xff); if (ret != OSAL_SUCCESS) { osal_printk("send message failed, sendMsg = %c\n", sendMsg[i]); } if (i == QUEUE_SIZE - 1) { osal_printk("\n"); } } memset(sendMsg, 0, QUEUE_SIZE); // osal_printk("\n"); osal_msleep(1000); index++; if (index == 10) { index = 0; strcpy(tempMsg, MSG); // 重置 tempMsg } }
} void recvMsgTask(void)
{ osal_printk("recvMsgTask start\r\n"); uint32_t ret = 0; char recvMsg[QUEUE_SIZE] = {0}; uint32_t msgSize = 1; // 定义缓冲区大小,单位字节 while (true) { for (int i = 0; i < QUEUE_SIZE; i++) { ret = osal_msg_queue_read_copy(queueID, &recvMsg[i], &msgSize, 0xff); if (ret != OSAL_SUCCESS) { osal_printk("recv message failed!\n", recvMsg[i]); break; } else { osal_printk("%c", recvMsg[i]); } } // osal_printk("\n"); memset(recvMsg, 0, QUEUE_SIZE); osal_msleep(500); }
} void blinkyInit(void)
{ uapi_gpio_set_isr_mode(BLINKY_PIN, HAL_PIO_FUNC_GPIO); uapi_gpio_set_dir(BLINKY_PIN, GPIO_DIRECTION_OUTPUT); uapi_gpio_set_val(BLINKY_PIN, GPIO_LEVEL_LOW);
} void sysInit(void)
{ blinkyInit(); uint32_t ret = 0; ret = osal_msg_queue_create(QUEUE_NAME, QUEUE_SIZE, &queueID, 0, QUEUE_NODE_SIZE); // 创建消息队列 if (ret != OSAL_SUCCESS) { osal_printk("create message queue failed\n"); } osal_task *sendMsgTaskHandle = NULL; osal_task *recvMsgTaskHandle = NULL; osal_kthread_lock(); sendMsgTaskHandle = osal_kthread_create((osal_kthread_handler)sendMsgTask, NULL, "sendTask", MSESSAGE_TASK_STACK_SIZE); if (ret != OSAL_SUCCESS) { osal_printk("create sendMsgTask failed\n"); } else { osal_kthread_set_priority(sendMsgTaskHandle, OSAL_TASK_PRIORITY_MIDDLE); // 设置任务优先级 osal_kfree(sendMsgTaskHandle); } recvMsgTaskHandle = osal_kthread_create((osal_kthread_handler)recvMsgTask, NULL, "recvTask", MSESSAGE_TASK_STACK_SIZE); if (ret != OSAL_SUCCESS) { osal_kthread_set_priority(recvMsgTaskHandle, OSAL_TASK_PRIORITY_MIDDLE); // 设置任务优先级 osal_kfree(recvMsgTaskHandle); } osal_kthread_unlock();
} app_run(sysInit);
SLE 透传 demo
基于小熊派官方提供的 sle_uart
demo 进行修改,结合编写的“通过 Queue 实现流式传输 demo”,实现数据透传
仓库链接:bearpi-pico_h3863 - Gitee
项目链接:sle_uart - Gitee
实验原理:将获取到的数据逐字符发出,以实现,流式传输的效果
关键点:
- SLE 协议栈
- SLE Server 和 Client 链接
- 数据处理
任务:
- 测试星闪通信是否正常
- 通过 UART 向 Server 端输入
姓名,学号,年龄
数据,处理后发送给 Client- 在数据前加上前缀,使其格式变为
name:姓名,num:学号,age:年龄
- Client 根据接收到的数据的最后一位的奇偶点亮板载 LED(奇数点亮,偶数熄灭)
- 在数据前加上前缀,使其格式变为
Workflow
项目文件结构
f:\ws63\src\application\samples\myTest\sle_uart_test\
├── src
│ ├──sle_uart.c
│ ├──sle_uart_server.c
│ ├──sle_uart_server_adv.c
│ └──sle_uart_client.c
├── include
│ ├── sle_uart_server.h
│ ├── sle_uart_server_adv.h
│ └── sle_uart_client.h
├── config
│ └── Kconfig
├── build
├── doc
│ └── README.md
└── CMakeLists.txt
Code
config SAMPLE_SUPPORT_SLE_UART_TEST
bool
prompt "Support SLE UART sample."
default n
depends on ENABLE_MYTEST
help This option means support SLE UART Sample. if SAMPLE_SUPPORT_SLE_UART_TEST
menu "SLE UART Sample Configuration"
osource "application/samples/myTest/sle_uart_test/Kconfig"
endmenu
endif
sle_uart. c
/*** Copyright (c) HiSilicon (Shanghai) Technologies Co., Ltd. 2023-2023. All rights reserved.** Description: SLE UART Sample Source. \n** History: \n* 2023-07-17, Create file. \n* * Code Comments: Written by Lamonce.* Last Modified: April 9, 2025 \n** Introduction:* This file implements a UART data transmission example based on the SLE* low-latency communication protocol. The code supports two working modes: Server and Client,* selected through macro definitions. The Server broadcasts its existence and accepts Client* connection requests, handling data exchange; the Client discovers, connects to the Server,* and performs data transmission. This example demonstrates how to configure UART,* initialize the SLE protocol stack, establish connections, and implement bidirectional* data transfer.** 简介:* 本文件实现了基于 SLE 低延迟通信协议的UART(串口)数据传输示例。* 代码支持两种工作模式:Server 端和 Client 端,通过宏定义进行选择。* Server 端负责广播自身存在并接收 Client 连接请求,处理数据交换;* Client 端负责发现、连接 Server 并完成数据传输。* 该示例展示了如何配置串口、初始化 SLE 协议栈、建立连接并实现双向数据传输。** DISCLAIMER:* This code is provided for reference and learning purposes only.* No warranty of correctness, completeness, or suitability for any purpose is provided.* Before using in production environments, please review and test thoroughly.** 免责声明:* 本文件中的代码及注释仅供学习和参考使用,不保证其在所有环境下的正确性和完整性。* 在实际项目中使用前,请根据具体需求进行适当的修改和测试。**/#include "common_def.h" // 常用函数定义
#include "soc_osal.h" // 硬件抽象层
#include "app_init.h" // 应用初始化
#include "pinctrl.h" // 引脚控制
#include "uart.h" // 串口控制
// #include "pm_clock.h" // 时钟控制
#include "sle_low_latency.h" // 低延迟通信框架// ---- 判断设备类型 ----#if defined(CONFIG_SAMPLE_SUPPORT_SLE_UART_SERVER_TEST) // Server 端测试
#include "securec.h" // 安全函数库,用于替代标准 C 库中不安全的函数
#include "sle_uart_server.h" // Server 端头文件
#include "sle_uart_server_adv.h" // Server 端广播相关头文件
#include "sle_device_discovery.h" // 设备发现相关头文件
#include "sle_errcode.h" // 错误码定义#elif defined(CONFIG_SAMPLE_SUPPORT_SLE_UART_CLIENT_TEST) // Client 端测试
#define SLE_UART_TASK_STACK_SIZE 0x600 // 任务栈大小
#include "sle_connection_manager.h" // 连接管理
#include "sle_ssap_client.h" // ssap 客户端
#include "sle_uart_client.h" // Client 端头文件
#endif /* CONFIG_SAMPLE_SUPPORT_SLE_UART_CLIENT */// ---- 设备类型判断结束 ----// ---- 串口配置 ----#define SLE_UART_TASK_PRIO 28 // 任务优先级
#define SLE_UART_TASK_DURATION_MS 2000 // 任务休眠时间
#define SLE_UART_BAUDRATE 115200 // 串口波特率
#define SLE_UART_TRANSFER_SIZE 512 // 串口传输缓冲区大小static uint8_t g_app_uart_rx_buff[SLE_UART_TRANSFER_SIZE] = {0}; // 串口接收缓冲区// UART 缓冲区配置
static uart_buffer_config_t g_app_uart_buffer_config = {.rx_buffer = g_app_uart_rx_buff,.rx_buffer_size = SLE_UART_TRANSFER_SIZE};// UART 引脚配置
static void uart_init_pin(void)
{// 判断当前使用的串口,串口号定义见 Kconfigif (CONFIG_SLE_UART_BUS == 0){// 引脚模式配置,引脚号定义见 Kconfig// 引脚连接方式参考:// https://www.bearpi.cn/core_board/bearpi/pico/h3863/hardware/%E5%8E%9F%E7%90%86%E5%9B%BE.html#%F0%9F%93%9C-%E5%8E%9F%E7%90%86%E5%9B%BE// 引脚模式参考:// https://gitee.com/HiSpark/fbb_ws63/blob/master/docs/board/IO%E5%A4%8D%E7%94%A8%E5%85%B3%E7%B3%BB.mduapi_pin_set_mode(CONFIG_UART_TXD_PIN, PIN_MODE_1);uapi_pin_set_mode(CONFIG_UART_RXD_PIN, PIN_MODE_1);}else if (CONFIG_SLE_UART_BUS == 1){uapi_pin_set_mode(CONFIG_UART_TXD_PIN, PIN_MODE_1);uapi_pin_set_mode(CONFIG_UART_RXD_PIN, PIN_MODE_1);}
}// UART 参数配置
static void uart_init_config(void)
{// 数据帧格式配置uart_attr_t attr = {.baud_rate = SLE_UART_BAUDRATE, // 波特率.data_bits = UART_DATA_BIT_8, // 数据位长度.stop_bits = UART_STOP_BIT_1, // 停止位长度.parity = UART_PARITY_NONE}; // 校验位长度// 引脚定义,在 ws63 上,txd 和 rxd 不可自定义,应参考硬件设计// 此处引脚定义无效uart_pin_config_t pin_config = {.tx_pin = CONFIG_UART_TXD_PIN,.rx_pin = CONFIG_UART_RXD_PIN,.cts_pin = PIN_NONE,.rts_pin = PIN_NONE};uapi_uart_deinit(CONFIG_SLE_UART_BUS); // 反初始化串口uapi_uart_init(CONFIG_SLE_UART_BUS, &pin_config, &attr, NULL, &g_app_uart_buffer_config); // 初始化串口
}// ---- 串口配置结束 ----// ---- 任务函数 ----
// 根据宏定义判断当前设备类型,启用对应的任务函数// Server 端
#if defined(CONFIG_SAMPLE_SUPPORT_SLE_UART_SERVER_TEST)
#define SLE_UART_SERVER_DELAY_COUNT 5#define SLE_UART_TASK_STACK_SIZE 0x1200 // 任务栈大小
#define SLE_ADV_HANDLE_DEFAULT 1 // 广播句柄
#define SLE_UART_SERVER_MSG_QUEUE_LEN 5 // 消息队列长度
#define SLE_UART_SERVER_MSG_QUEUE_MAX_SIZE 32 // 消息节点大小
#define SLE_UART_SERVER_QUEUE_DELAY 0xFFFFFFFF // 消息队列延时,此处设置为最大值
#define SLE_UART_SERVER_BUFF_MAX_SIZE 800unsigned long g_sle_uart_server_msgqueue_id; // 消息队列句柄
#define SLE_UART_SERVER_LOG "[sle uart server]" // 日志前缀// 读请求回调函数
static void ssaps_server_read_request_cbk(uint8_t server_id, uint16_t conn_id, ssaps_req_read_cb_t *read_cb_para,errcode_t status)
{osal_printk("%s ssaps read request cbk callback server_id:%x, conn_id:%x, handle:%x, status:%x\r\n",SLE_UART_SERVER_LOG, server_id, conn_id, read_cb_para->handle, status);
}// 写请求回调函数
static void ssaps_server_write_request_cbk(uint8_t server_id, uint16_t conn_id, ssaps_req_write_cb_t *write_cb_para,errcode_t status)
{osal_printk("%s ssaps write request callback cbk server_id:%x, conn_id:%x, handle:%x, status:%x\r\n",SLE_UART_SERVER_LOG, server_id, conn_id, write_cb_para->handle, status);// 判断写入数据的长度和内容if ((write_cb_para->length > 0) && write_cb_para->value){// 打印接收的数据osal_printk("\n sle uart received data : %s\r\n", write_cb_para->value);uapi_uart_write(CONFIG_SLE_UART_BUS, (uint8_t *)write_cb_para->value, write_cb_para->length, 0);}
}// UART 接收中断处理函数
static void sle_uart_server_read_int_handler(const void *buffer, uint16_t length, bool error)
{unused(error);// 检查 Client 端是否连接if (sle_uart_client_is_connected()){// 发送数据到 Client 端sle_uart_server_send_report_by_handle(buffer, length);}else{osal_printk("%s sle client is not connected! \r\n", SLE_UART_SERVER_LOG);}
}// 创建消息队列
static void sle_uart_server_create_msgqueue(void)
{if (osal_msg_queue_create("sle_uart_server_msgqueue", // 队列名称,保留SLE_UART_SERVER_MSG_QUEUE_LEN, // 队列长度(unsigned long *)&g_sle_uart_server_msgqueue_id, // 成功创建的队列控制结构的 ID0, // 队列模式,保留SLE_UART_SERVER_MSG_QUEUE_MAX_SIZE) != OSAL_SUCCESS) // 消息节点大小{osal_printk("^%s sle_uart_server_create_msgqueue message queue create failed!\n", SLE_UART_SERVER_LOG);}
}// 删除消息队列
static void sle_uart_server_delete_msgqueue(void)
{osal_msg_queue_delete(g_sle_uart_server_msgqueue_id);
}// 写入消息队列
static void sle_uart_server_write_msgqueue(uint8_t *buffer_addr, uint16_t buffer_size)
{osal_msg_queue_write_copy(g_sle_uart_server_msgqueue_id, (void *)buffer_addr,(uint32_t)buffer_size, 0);
}// 从消息队列读取数据
static int32_t sle_uart_server_receive_msgqueue(uint8_t *buffer_addr, uint32_t *buffer_size)
{return osal_msg_queue_read_copy(g_sle_uart_server_msgqueue_id, (void *)buffer_addr,buffer_size, SLE_UART_SERVER_QUEUE_DELAY);
}// 初始化接收缓冲区,实际操作为清空缓冲区(全部置为 0)
static void sle_uart_server_rx_buf_init(uint8_t *buffer_addr, uint32_t *buffer_size)
{*buffer_size = SLE_UART_SERVER_MSG_QUEUE_MAX_SIZE;(void)memset_s(buffer_addr, *buffer_size, 0, *buffer_size);
}// 任务函数
static void *sle_uart_server_task(const char *arg)
{unused(arg);uint8_t rx_buf[SLE_UART_SERVER_MSG_QUEUE_MAX_SIZE] = {0}; // 定义接收缓冲区uint32_t rx_length = SLE_UART_SERVER_MSG_QUEUE_MAX_SIZE; // 接收缓冲区长度uint8_t sle_connect_state[] = "sle_dis_connect"; // 连接状态sle_uart_server_create_msgqueue(); // 创建消息队列sle_uart_server_register_msg(sle_uart_server_write_msgqueue); // 注册消息队列sle_uart_server_init(ssaps_server_read_request_cbk, ssaps_server_write_request_cbk); // 初始化 Server 端// 这是一个高度抽象的函数,将 SLE Server 的多种回调函数的注册、启动广播等操作进行集成/* UART pinmux. */// 初始化引脚uart_init_pin();/* UART init config. */// 初始化串口配置uart_init_config();// 反注册串口回调函数uapi_uart_unregister_rx_callback(CONFIG_SLE_UART_BUS);// 注册接收回调函数,这个回调函数会根据触发条件和Size触发errcode_t ret = uapi_uart_register_rx_callback(CONFIG_SLE_UART_BUS, // 串口号UART_RX_CONDITION_FULL_OR_IDLE, // 触发条件,参见 uart_rx_condition_t,如果接收缓存已满,或者接收的数据量到达指定的数据长度,就触发数据接收回调1, // 如果触发条件涉及到数据长度,这个参数就表示需要的数据长度sle_uart_server_read_int_handler); // 接收数据的回调函数// 检查注册结果if (ret != ERRCODE_SUCC){osal_printk("%s Register uart callback fail.[%x]\r\n", SLE_UART_SERVER_LOG, ret);return NULL;}// 进入死循环while (1){// 清空接收缓冲区sle_uart_server_rx_buf_init(rx_buf, &rx_length);// 接收数据sle_uart_server_receive_msgqueue(rx_buf, &rx_length);// 检查接收数据是否为定义的未连接状态,若未连接,则开始广播if (strncmp((const char *)rx_buf, (const char *)sle_connect_state, sizeof(sle_connect_state)) == 0){ret = sle_start_announce(SLE_ADV_HANDLE_DEFAULT);if (ret != ERRCODE_SLE_SUCCESS){osal_printk("%s sle_connect_state_changed_cbk,sle_start_announce fail :%02x\r\n",SLE_UART_SERVER_LOG, ret);}}osal_msleep(SLE_UART_TASK_DURATION_MS); // 休眠一段时间,然后再次检查连接状态}// 删除消息队列sle_uart_server_delete_msgqueue();return NULL;
}// Client 端
#elif defined(CONFIG_SAMPLE_SUPPORT_SLE_UART_CLIENT_TEST)/** Notification and Indication* 1. Notification: Client 端不需要响应,Server 端发送数据后直接接收* 2. Indication: Client 端需要响应,Server 端发送数据后需要等待 Client 端的响应*/// 通知回调函数
void sle_uart_notification_cb(uint8_t client_id, uint16_t conn_id, ssapc_handle_value_t *data,errcode_t status)
{unused(client_id);unused(conn_id);unused(status);osal_printk("\n sle uart recived data : %s\r\n", data->data);uapi_uart_write(CONFIG_SLE_UART_BUS, (uint8_t *)(data->data), data->data_len, 0);
}// 指示回调函数
void sle_uart_indication_cb(uint8_t client_id, uint16_t conn_id, ssapc_handle_value_t *data,errcode_t status)
{unused(client_id);unused(conn_id);unused(status);osal_printk("\n sle uart recived data : %s\r\n", data->data);uapi_uart_write(CONFIG_SLE_UART_BUS, (uint8_t *)(data->data), data->data_len, 0);
}// UART 接收回调函数
static void sle_uart_client_read_int_handler(const void *buffer, uint16_t length, bool error)
{unused(error);ssapc_write_param_t *sle_uart_send_param = get_g_sle_uart_send_param();uint16_t g_sle_uart_conn_id = get_g_sle_uart_conn_id();sle_uart_send_param->data_len = length;sle_uart_send_param->data = (uint8_t *)buffer;ssapc_write_req(0, g_sle_uart_conn_id, sle_uart_send_param);
}// Client 端任务函数
static void *sle_uart_client_task(const char *arg)
{unused(arg);/* UART pinmux. */uart_init_pin();/* UART init config. */uart_init_config();uapi_uart_unregister_rx_callback(CONFIG_SLE_UART_BUS);// 注册接收回调函数,这个回调函数会根据触发条件和Size触发errcode_t ret = uapi_uart_register_rx_callback(CONFIG_SLE_UART_BUS,UART_RX_CONDITION_FULL_OR_IDLE,1, sle_uart_client_read_int_handler);// 初始化 Client 端// 这是一个高度抽象的函数,将 SLE Client 的多种回调函数的注册进行集成sle_uart_client_init(sle_uart_notification_cb, sle_uart_indication_cb);if (ret != ERRCODE_SUCC){osal_printk("Register uart callback fail.");return NULL;}return NULL;
}
#endif /* CONFIG_SAMPLE_SUPPORT_SLE_UART_CLIENT */// ---- 任务函数结束 ----// ---- 任务入口函数 ----
static void sle_uart_entry(void)
{osal_task *task_handle = NULL; // 任务句柄osal_kthread_lock(); // 锁任务调度// 通过宏定义判断当前设备类型,创建对应的任务,返回任务句柄
#if defined(CONFIG_SAMPLE_SUPPORT_SLE_UART_SERVER_TEST) // Server 端测试task_handle = osal_kthread_create((osal_kthread_handler)sle_uart_server_task, 0, "SLEUartServerTask",SLE_UART_TASK_STACK_SIZE);#elif defined(CONFIG_SAMPLE_SUPPORT_SLE_UART_CLIENT_TEST) // Client 端测试task_handle = osal_kthread_create((osal_kthread_handler)sle_uart_client_task, 0, "SLEUartDongleTask",SLE_UART_TASK_STACK_SIZE);
#endif /* CONFIG_SAMPLE_SUPPORT_SLE_UART_CLIENT */if (task_handle != NULL) // 判断任务是否创建成功{osal_kthread_set_priority(task_handle, SLE_UART_TASK_PRIO); // 设置任务优先级}osal_kthread_unlock(); // 解锁任务调度
}
// ---- 任务入口函数结束 ----/* Run the sle_uart_entry. */
// 程序入口函数
app_run(sle_uart_entry);
Server
sle_uart_server. c
/*** Copyright (c) HiSilicon (Shanghai) Technologies Co., Ltd. 2023-2023. All rights reserved.** Description: SLE UART Server Source. \n** History: \n* 2023-07-17, Create file. \n** Code Comments: Written by Lamonce.* Last Modified: April 10, 2025 \n** Introduction:* This file implements the server-side functionality for SLE UART* communications. It provides a complete GATT server implementation with service,* characteristic, and descriptor management. The server broadcasts its availability,* accepts client connections, handles pairing, and supports bidirectional data transmission* through both UUID-based and handle-based methods. Key features include connection state* management, callback registration for various events, and MTU negotiation.** 简介:* 本文件实现了 SLE UART 通信的服务端功能。它提供了完整的 GATT 服务器* 实现,包括服务、特征和描述符管理。服务端广播自身可用性,接受客户端连接请求,处理配对过程,* 并通过基于 UUID 和基于句柄的方法支持双向数据传输。主要功能包括连接状态管理、各类事件的* 回调注册以及 MTU 协商。整个实现遵循星闪低功耗通信协议规范,确保高效、可靠的设备间通信。** DISCLAIMER:* This code is provided for reference and learning purposes only.* No warranty of correctness, completeness, or suitability for any purpose is provided.* Before using in production environments, please review and test thoroughly.** 免责声明:* 本文件中的代码及注释仅供学习和参考使用,不保证其在所有环境下的正确性和完整性。* 在实际项目中使用前,请根据具体需求进行适当的修改和测试。**/#include "common_def.h" // 常用函数定义
#include "securec.h" // 安全函数库,用于替代标准 C 库中不安全的函数
#include "soc_osal.h" // 硬件抽象层
#include "sle_errcode.h" // 错误码定义
#include "sle_connection_manager.h" // 连接管理
#include "sle_device_discovery.h" // 设备发现相关
#include "sle_uart_server_adv.h" // 广播相关头文件
#include "sle_uart_server.h" // Server 端头文件#define OCTET_BIT_LEN 8
#define UUID_LEN_2 2 // 16 位 UUID 长度
#define UUID_INDEX 14 // UUID 最后两字节索引
#define BT_INDEX_4 4
#define BT_INDEX_0 0
#define UART_BUFF_LENGTH 0x100 // UART 缓冲区长度/* 广播ID */
#define SLE_ADV_HANDLE_DEFAULT 1 // 设备公开 ID
/* sle server app uuid for test */
static char g_sle_uuid_app_uuid[UUID_LEN_2] = {0x12, 0x34}; // 服务端应用 UUID
/* server notify property uuid for test */
static char g_sle_property_value[OCTET_BIT_LEN] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; // 特征值
/* sle connect acb handle */
static uint16_t g_sle_conn_hdl = 0; // 连接句柄
/* sle server handle */
static uint8_t g_server_id = 0; // 服务端 ID
/* sle service handle */
static uint16_t g_service_handle = 0; // 服务句柄
/* sle ntf property handle */
static uint16_t g_property_handle = 0; // 特征句柄
/* sle pair acb handle */
uint16_t g_sle_pair_hdl; // 配对句柄#define UUID_16BIT_LEN 2 // 16 位 UUID 长度
#define UUID_128BIT_LEN 16 // 128 位 UUID 长度
#define sample_at_log_print(fmt, args...) osal_printk(fmt, ##args)
#define SLE_UART_SERVER_LOG "[sle uart server]" // 日志前缀
#define SLE_SERVER_INIT_DELAY_MS 1000 // 延时 1 秒
static sle_uart_server_msg_queue g_sle_uart_server_msg_queue = NULL; // 消息队列// 星闪标准服务标识 基础标识(Base UUID):37BEA880-FC70-11EA-B720-000000000000
// 带上这个基础标识表示这个星闪服务
// Base UUID 后面6字节是媒体接入层标识(在某个网段内,分配给网络设备的用于网络通信寻址的唯一标识)
// 在用于产品开发时,厂商需要向 SparkLink 组织申请
static uint8_t g_sle_uart_base[] = {0x37, 0xBE, 0xA8, 0x80, 0xFC, 0x70, 0x11, 0xEA,0xB7, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};// 获取连接句柄
uint16_t get_connect_id(void)
{return g_sle_conn_hdl;
}/*** @brief 将16位整数(uint16_t)以小端字节序的方式存储到内存中** @param _ptr 低地址指针* @param data 数据** @attention 将16位整数 data 的低8位(最低有效字节)存储到 _ptr 指向的地址* @attention 将16位整数 data 的高8位(最高有效字节)存储到 _ptr+1 指向的地址*/
static void encode2byte_little(uint8_t *_ptr, uint16_t data)
{*(uint8_t *)((_ptr) + 1) = (uint8_t)((data) >> 0x8);*(uint8_t *)(_ptr) = (uint8_t)(data);
}// 设置服务 UUID 的基础值
static void sle_uuid_set_base(sle_uuid_t *out)
{errcode_t ret;// 复制 UUID 的基础值ret = memcpy_s(out->uuid, SLE_UUID_LEN, g_sle_uart_base, SLE_UUID_LEN);if (ret != EOK){sample_at_log_print("%s sle_uuid_set_base memcpy fail\n", SLE_UART_SERVER_LOG);out->len = 0;return;}out->len = UUID_LEN_2; // 设置 UUID 的长度为 2
}// 设置长度为 2 的服务 UUID 的值
static void sle_uuid_setu2(uint16_t u2, sle_uuid_t *out)
{sle_uuid_set_base(out); // 设置 UUID 的基础值out->len = UUID_LEN_2; // 设置 UUID 的长度为 2encode2byte_little(&out->uuid[UUID_INDEX], u2); // 将 16 位整数以小端字节序存储到 UUID 末尾
}// 输出 UUID 的值
static void sle_uart_uuid_print(sle_uuid_t *uuid)
{if (uuid == NULL){sample_at_log_print("%s uuid_print,uuid is null\r\n", SLE_UART_SERVER_LOG);return;}// 检查 UUID 长度if (uuid->len == UUID_16BIT_LEN){sample_at_log_print("%s uuid: %02x %02x.\n", SLE_UART_SERVER_LOG,uuid->uuid[14], uuid->uuid[15]); /* 14 15: uuid index */}else if (uuid->len == UUID_128BIT_LEN){sample_at_log_print("%s uuid: \n", SLE_UART_SERVER_LOG); /* 14 15: uuid index */sample_at_log_print("%s 0x%02x 0x%02x 0x%02x \n", SLE_UART_SERVER_LOG, uuid->uuid[0], uuid->uuid[1],uuid->uuid[2], uuid->uuid[3]);sample_at_log_print("%s 0x%02x 0x%02x 0x%02x \n", SLE_UART_SERVER_LOG, uuid->uuid[4], uuid->uuid[5],uuid->uuid[6], uuid->uuid[7]);sample_at_log_print("%s 0x%02x 0x%02x 0x%02x \n", SLE_UART_SERVER_LOG, uuid->uuid[8], uuid->uuid[9],uuid->uuid[10], uuid->uuid[11]);sample_at_log_print("%s 0x%02x 0x%02x 0x%02x \n", SLE_UART_SERVER_LOG, uuid->uuid[12], uuid->uuid[13],uuid->uuid[14], uuid->uuid[15]);}
}// -------- ssapc 注册回调函数 --------/*** @brief MTU 改变回调函数** @param server_id 服务 ID* @param conn_id 连接 ID* @param mtu_size MTU 大小* @param status 状态码*/
static void ssaps_mtu_changed_cbk(uint8_t server_id, uint16_t conn_id, ssap_exchange_info_t *mtu_size,errcode_t status)
{sample_at_log_print("%s ssaps ssaps_mtu_changed_cbk callback server_id:%x, conn_id:%x, mtu_size:%x, status:%x\r\n",SLE_UART_SERVER_LOG, server_id, conn_id, mtu_size->mtu_size, status);if (g_sle_pair_hdl == 0){g_sle_pair_hdl = conn_id + 1;}
}/*** @brief 服务启动回调函数** @param server_id 服务 ID* @param handle 服务句柄* @param status 状态码*/
static void ssaps_start_service_cbk(uint8_t server_id, uint16_t handle, errcode_t status)
{sample_at_log_print("%s start service cbk callback server_id:%d, handle:%x, status:%x\r\n", SLE_UART_SERVER_LOG,server_id, handle, status);
}/*** @brief ssaps 添加服务回调函数** @param server_id 服务 ID* @param uuid 服务 UUID* @param handle 服务句柄* @param status 状态码*/
static void ssaps_add_service_cbk(uint8_t server_id, sle_uuid_t *uuid, uint16_t handle, errcode_t status)
{sample_at_log_print("%s add service cbk callback server_id:%x, handle:%x, status:%x\r\n", SLE_UART_SERVER_LOG,server_id, handle, status);sle_uart_uuid_print(uuid);
}/*** @brief 服务特征添加回调函数** @param server_id 服务 ID* @param uuid 服务 UUID* @param service_handle 服务句柄* @param handle 特征句柄* @param status 状态码*/
static void ssaps_add_property_cbk(uint8_t server_id, sle_uuid_t *uuid, uint16_t service_handle,uint16_t handle, errcode_t status)
{sample_at_log_print("%s add property cbk callback server_id:%x, service_handle:%x,handle:%x, status:%x\r\n",SLE_UART_SERVER_LOG, server_id, service_handle, handle, status);sle_uart_uuid_print(uuid);
}/*** @brief 服务描述符添加回调函数** @param server_id 服务 ID* @param uuid 服务 UUID* @param service_handle 服务句柄* @param property_handle 特征句柄* @param status 状态码*/
static void ssaps_add_descriptor_cbk(uint8_t server_id, sle_uuid_t *uuid, uint16_t service_handle,uint16_t property_handle, errcode_t status)
{sample_at_log_print("%s add descriptor cbk callback server_id:%x, service_handle:%x, property_handle:%x, \status:%x\r\n",SLE_UART_SERVER_LOG, server_id, service_handle, property_handle, status);sle_uart_uuid_print(uuid);
}/*** @brief 删除所有服务回调函数** @param server_id 服务 ID* @param status 状态码*/
static void ssaps_delete_all_service_cbk(uint8_t server_id, errcode_t status)
{sample_at_log_print("%s delete all service callback server_id:%x, status:%x\r\n", SLE_UART_SERVER_LOG,server_id, status);
}// ssaps 注册回调函数
static errcode_t sle_ssaps_register_cbks(ssaps_read_request_callback ssaps_read_callback, ssaps_write_request_callbackssaps_write_callback)
{errcode_t ret;ssaps_callbacks_t ssaps_cbk = {0}; // 回调函数结构体ssaps_cbk.add_service_cb = ssaps_add_service_cbk; // 添加服务回调函数ssaps_cbk.add_property_cb = ssaps_add_property_cbk; // 添加特征回调函数ssaps_cbk.add_descriptor_cb = ssaps_add_descriptor_cbk; // 添加描述符回调函数ssaps_cbk.start_service_cb = ssaps_start_service_cbk; // 服务启动回调函数ssaps_cbk.delete_all_service_cb = ssaps_delete_all_service_cbk; // 删除所有服务回调函数ssaps_cbk.mtu_changed_cb = ssaps_mtu_changed_cbk; // MTU 改变回调函数ssaps_cbk.read_request_cb = ssaps_read_callback; // 读请求回调函数ssaps_cbk.write_request_cb = ssaps_write_callback; // 写请求回调函数ret = ssaps_register_callbacks(&ssaps_cbk); // 注册回调函数if (ret != ERRCODE_SLE_SUCCESS){sample_at_log_print("%s sle_ssaps_register_cbks,ssaps_register_callbacks fail :%x\r\n", SLE_UART_SERVER_LOG,ret);return ret;}return ERRCODE_SLE_SUCCESS;
}// -------- ssapc 注册回调函数结束 ----// 服务添加
static errcode_t sle_uuid_server_service_add(void)
{errcode_t ret;sle_uuid_t service_uuid = {0}; // 创建服务 UUID 结构体sle_uuid_setu2(SLE_UUID_SERVER_SERVICE, &service_uuid); // 设置服务 UUIDret = ssaps_add_service_sync(g_server_id, &service_uuid, 1, &g_service_handle); // 添加一个ssap服务if (ret != ERRCODE_SLE_SUCCESS){sample_at_log_print("%s sle uuid add service fail, ret:%x\r\n", SLE_UART_SERVER_LOG, ret);return ERRCODE_SLE_FAIL;}return ERRCODE_SLE_SUCCESS;
}// 添加特征
static errcode_t sle_uuid_server_property_add(void)
{errcode_t ret;ssaps_property_info_t property = {0}; // 创建特征信息结构体ssaps_desc_info_t descriptor = {0}; // 创建描述符信息结构体uint8_t ntf_value[] = {0x01, 0x0}; // 描述符数据property.permissions = SLE_UUID_TEST_PROPERTIES; // 特征权限,此 demo 设置为可读可写property.operate_indication = SSAP_OPERATE_INDICATION_BIT_READ | SSAP_OPERATE_INDICATION_BIT_NOTIFY; // 操作指示,数据值可被读取,通过通知方式传递给客户端sle_uuid_setu2(SLE_UUID_SERVER_NTF_REPORT, &property.uuid); // 设置特征 UUID// 分配内存给特征值(value 为指向特征值的指针)property.value = (uint8_t *)osal_vmalloc(sizeof(g_sle_property_value));if (property.value == NULL) // 检查内存分配是否成功{return ERRCODE_SLE_FAIL;}if (memcpy_s(property.value, sizeof(g_sle_property_value), g_sle_property_value,sizeof(g_sle_property_value)) != EOK) // 复制特征值{osal_vfree(property.value); // 当复制失败时,释放内存return ERRCODE_SLE_FAIL;}ret = ssaps_add_property_sync(g_server_id, g_service_handle, &property, &g_property_handle); // 添加特征,并获取特征句柄if (ret != ERRCODE_SLE_SUCCESS){sample_at_log_print("%s sle uart add property fail, ret:%x\r\n", SLE_UART_SERVER_LOG, ret);osal_vfree(property.value); // 当添加特征失败时,释放内存return ERRCODE_SLE_FAIL;}descriptor.permissions = SLE_UUID_TEST_DESCRIPTOR; // 特征权限,此 demo 设置为可读可写descriptor.type = SSAP_DESCRIPTOR_USER_DESCRIPTION; // 描述符类型,属性说明描述符descriptor.operate_indication = SSAP_OPERATE_INDICATION_BIT_READ | SSAP_OPERATE_INDICATION_BIT_WRITE; // 操作指示,数据值可被读取和写入,写入后产生反馈给客户端descriptor.value = ntf_value; // 描述符数据descriptor.value_len = sizeof(ntf_value); // 描述符数据长度// 添加描述符ret = ssaps_add_descriptor_sync(g_server_id, g_service_handle, g_property_handle, &descriptor);if (ret != ERRCODE_SLE_SUCCESS){sample_at_log_print("%s sle uart add descriptor fail, ret:%x\r\n", SLE_UART_SERVER_LOG, ret);osal_vfree(property.value); // 若添加描述符失败,释放特征值内存osal_vfree(descriptor.value); // 释放描述符值内存return ERRCODE_SLE_FAIL;}// 添加特征成功后,释放特征值内存osal_vfree(property.value);return ERRCODE_SLE_SUCCESS;
}// 添加服务
static errcode_t sle_uart_server_add(void)
{errcode_t ret;sle_uuid_t app_uuid = {0}; // 创建应用 UUID 结构体sample_at_log_print("%s sle uart add service in\r\n", SLE_UART_SERVER_LOG);app_uuid.len = sizeof(g_sle_uuid_app_uuid); // 设置应用 UUID 长度// 复制应用 UUIDif (memcpy_s(app_uuid.uuid, app_uuid.len, g_sle_uuid_app_uuid, sizeof(g_sle_uuid_app_uuid)) != EOK){return ERRCODE_SLE_FAIL;}ssaps_register_server(&app_uuid, &g_server_id); // 注册 ssap 服务端,参数:app_uuid:上层应用uuid,g_server_id:服务端ID// 添加服务if (sle_uuid_server_service_add() != ERRCODE_SLE_SUCCESS){ssaps_unregister_server(g_server_id); // 如果添加服务失败,注销服务端return ERRCODE_SLE_FAIL;}// 添加特征if (sle_uuid_server_property_add() != ERRCODE_SLE_SUCCESS){ssaps_unregister_server(g_server_id); // 如果添加特征失败,注销服务端return ERRCODE_SLE_FAIL;}sample_at_log_print("%s sle uart add service, server_id:%x, service_handle:%x, property_handle:%x\r\n",SLE_UART_SERVER_LOG, g_server_id, g_service_handle, g_property_handle);// 启动服务ret = ssaps_start_service(g_server_id, g_service_handle);if (ret != ERRCODE_SLE_SUCCESS){sample_at_log_print("%s sle uart add service fail, ret:%x\r\n", SLE_UART_SERVER_LOG, ret);return ERRCODE_SLE_FAIL;}sample_at_log_print("%s sle uart add service out\r\n", SLE_UART_SERVER_LOG);return ERRCODE_SLE_SUCCESS;
}/* device通过uuid向host发送数据:report */
/*** @brief Server 端通过 UUID 向 Host(Client) 发送数据** @param data 发送的数据* @param len 数据长度* @return errcode_t*/
errcode_t sle_uart_server_send_report_by_uuid(const uint8_t *data, uint8_t len)
{errcode_t ret;ssaps_ntf_ind_by_uuid_t param = {0}; // 创建通知/指示参数结构体param.type = SSAP_PROPERTY_TYPE_VALUE; // 属性类型,特征值param.start_handle = g_service_handle; // 起始句柄param.end_handle = g_property_handle; // 结束句柄param.value_len = len; // 数据长度param.value = (uint8_t *)osal_vmalloc(len); // 动态分配内存给数据if (param.value == NULL) // 检查内存分配是否成功{sample_at_log_print("%s send report new fail\r\n", SLE_UART_SERVER_LOG);return ERRCODE_SLE_FAIL;}if (memcpy_s(param.value, param.value_len, data, len) != EOK) // 复制数据到参数{sample_at_log_print("%s send input report memcpy fail\r\n", SLE_UART_SERVER_LOG);osal_vfree(param.value); // 当复制失败时,释放内存return ERRCODE_SLE_FAIL;}sle_uuid_setu2(SLE_UUID_SERVER_NTF_REPORT, ¶m.uuid); // 设置 UUIDret = ssaps_notify_indicate_by_uuid(g_server_id, g_sle_conn_hdl, ¶m); // 发送通知/指示,具体发送状态取决于客户端特征配置描述符值if (ret != ERRCODE_SLE_SUCCESS) // 检查发送是否成功{sample_at_log_print("%s sle_uart_server_send_report_by_uuid,ssaps_notify_indicate_by_uuid fail :%x\r\n",SLE_UART_SERVER_LOG, ret);osal_vfree(param.value);return ret;}osal_vfree(param.value); // 释放内存return ERRCODE_SLE_SUCCESS;
}/* device通过handle向host发送数据:report */
/*** @brief Server 端通过句柄向 Host(Client) 发送数据** @param data 数据* @param len 数据长度* @return errcode_t*/
errcode_t sle_uart_server_send_report_by_handle(const uint8_t *data, uint16_t len)
{ssaps_ntf_ind_t param = {0};uint8_t receive_buf[UART_BUFF_LENGTH] = {0}; /* max receive length. */param.handle = g_property_handle;param.type = SSAP_PROPERTY_TYPE_VALUE;param.value = receive_buf;param.value_len = len;if (memcpy_s(param.value, param.value_len, data, len) != EOK){return ERRCODE_SLE_FAIL;}return ssaps_notify_indicate(g_server_id, g_sle_conn_hdl, ¶m);
}/*** @brief 连接状态改变回调函数** @param conn_id 连接 ID* @param addr 设备地址* @param conn_state 连接状态* @param pair_state 配对状态* @param disc_reason 断开连接的原因*/
static void sle_connect_state_changed_cbk(uint16_t conn_id, const sle_addr_t *addr,sle_acb_state_t conn_state, sle_pair_state_t pair_state, sle_disc_reason_t disc_reason)
{uint8_t sle_connect_state[] = "sle_dis_connect"; // 创建连接状态字符串,并初始化为 "sle_dis_connect"sample_at_log_print("%s connect state changed callback conn_id:0x%02x, conn_state:0x%x, pair_state:0x%x, \disc_reason:0x%x\r\n",SLE_UART_SERVER_LOG, conn_id, conn_state, pair_state, disc_reason);sample_at_log_print("%s connect state changed callback addr:%02x:**:**:**:%02x:%02x\r\n", SLE_UART_SERVER_LOG,addr->addr[BT_INDEX_0], addr->addr[BT_INDEX_4]);if (conn_state == SLE_ACB_STATE_CONNECTED) // 已连接{g_sle_conn_hdl = conn_id; // 更新连接句柄}else if (conn_state == SLE_ACB_STATE_DISCONNECTED) // 未连接{g_sle_conn_hdl = 0;g_sle_pair_hdl = 0;if (g_sle_uart_server_msg_queue != NULL){g_sle_uart_server_msg_queue(sle_connect_state, sizeof(sle_connect_state));}}
}// 配对完成回调函数
static void sle_pair_complete_cbk(uint16_t conn_id, const sle_addr_t *addr, errcode_t status)
{sample_at_log_print("%s pair complete conn_id:%02x, status:%x\r\n", SLE_UART_SERVER_LOG,conn_id, status);sample_at_log_print("%s pair complete addr:%02x:**:**:**:%02x:%02x\r\n", SLE_UART_SERVER_LOG,addr->addr[BT_INDEX_0], addr->addr[BT_INDEX_4]);g_sle_pair_hdl = conn_id + 1;ssap_exchange_info_t parameter = {0};parameter.mtu_size = 520;parameter.version = 1;ssaps_set_info(g_server_id, ¶meter);
}// 注册连接回调函数
static errcode_t sle_conn_register_cbks(void)
{errcode_t ret;sle_connection_callbacks_t conn_cbks = {0};conn_cbks.connect_state_changed_cb = sle_connect_state_changed_cbk; // 连接状态改变回调conn_cbks.pair_complete_cb = sle_pair_complete_cbk; // 配对完成回调ret = sle_connection_register_callbacks(&conn_cbks);if (ret != ERRCODE_SLE_SUCCESS){sample_at_log_print("%s sle_conn_register_cbks,sle_connection_register_callbacks fail :%x\r\n",SLE_UART_SERVER_LOG, ret);return ret;}return ERRCODE_SLE_SUCCESS;
}// 获取连接句柄
uint16_t sle_uart_client_is_connected(void)
{return g_sle_pair_hdl;
}/* 初始化uuid server */
errcode_t sle_uart_server_init(ssaps_read_request_callback ssaps_read_callback, ssaps_write_request_callbackssaps_write_callback)
{errcode_t ret;/* 使能SLE */if (enable_sle() != ERRCODE_SUCC){sample_at_log_print("[SLE Server] sle enbale fail !\r\n");return -1;}// 注册广播回调函数ret = sle_uart_announce_register_cbks();if (ret != ERRCODE_SLE_SUCCESS){sample_at_log_print("%s sle_uart_server_init,sle_uart_announce_register_cbks fail :%x\r\n",SLE_UART_SERVER_LOG, ret);return ret;}// 注册连接回调函数ret = sle_conn_register_cbks();if (ret != ERRCODE_SLE_SUCCESS){sample_at_log_print("%s sle_uart_server_init,sle_conn_register_cbks fail :%x\r\n", SLE_UART_SERVER_LOG, ret);return ret;}// 注册 ssaps 回调函数ret = sle_ssaps_register_cbks(ssaps_read_callback, ssaps_write_callback);if (ret != ERRCODE_SLE_SUCCESS){sample_at_log_print("%s sle_uart_server_init,sle_ssaps_register_cbks fail :%x\r\n", SLE_UART_SERVER_LOG, ret);return ret;}// 添加服务ret = sle_uart_server_add();if (ret != ERRCODE_SLE_SUCCESS){sample_at_log_print("%s sle_uart_server_init,sle_uart_server_add fail :%x\r\n", SLE_UART_SERVER_LOG, ret);return ret;}// 初始化广播ret = sle_uart_server_adv_init();if (ret != ERRCODE_SLE_SUCCESS){sample_at_log_print("%s sle_uart_server_init,sle_uart_server_adv_init fail :%x\r\n", SLE_UART_SERVER_LOG, ret);return ret;}sample_at_log_print("%s init ok\r\n", SLE_UART_SERVER_LOG);return ERRCODE_SLE_SUCCESS;
}void sle_uart_server_register_msg(sle_uart_server_msg_queue sle_uart_server_msg)
{g_sle_uart_server_msg_queue = sle_uart_server_msg;
}
sle_uart_server_adv. c
/*** Copyright (c) HiSilicon (Shanghai) Technologies Co., Ltd. 2023-2023. All rights reserved.** Description: sle adv config for sle uart server. \n** History: \n* 2023-07-17, Create file. \n** Code Comments: Written by Lamonce.* Last Modified: April 10, 2025 \n** Introduction:* This file implements the advertising functionality for SLE UART server communications.* It provides complete advertising configuration and management, including advertisement* parameters setup, advertisement data formatting, and scan response data configuration.* The module supports standard SLE advertising modes, handles advertisement state callbacks,* and manages the advertisement lifecycle. Key features include customizable advertisement* intervals, transmit power control, device name broadcasting, and service data advertising.** 简介:* 本文件实现了 SLE UART 服务端通信的广播功能。它提供了完整的广播配置和管理,* 包括广播参数设置、广播数据格式化和扫描响应数据配置。该模块支持标准 SLE 广播模式,* 处理广播状态回调,并管理广播生命周期。主要功能包括可自定义的广播间隔、发射功率控制、* 设备名称广播和服务数据广播。整个模块设计符合星闪低功耗通信协议规范,* 确保设备能被客户端高效发现和连接。** DISCLAIMER:* This code is provided for reference and learning purposes only.* No warranty of correctness, completeness, or suitability for any purpose is provided.* Before using in production environments, please review and test thoroughly.** 免责声明:* 本文件中的代码及注释仅供学习和参考使用,不保证其在所有环境下的正确性和完整性。* 在实际项目中使用前,请根据具体需求进行适当的修改和测试。**/
#include "securec.h" // 安全函数库,用于替代标准 C 库中不安全的函数
#include "errcode.h" // 错误码定义
#include "osal_addr.h" // 地址相关函数
#include "product.h" // 产品相关函数
#include "sle_common.h" // 公共函数定义
#include "sle_uart_server.h" // Server 端头文件
#include "sle_device_discovery.h" // 设备发现相关头文件
#include "sle_errcode.h" // 错误码定义
#include "osal_debug.h" // 调试相关函数
#include "osal_task.h" // 任务相关函数
#include "string.h" // 字符串相关函数
#include "sle_uart_server_adv.h" // Server 端广播相关头文件/* sle device name */
#define NAME_MAX_LENGTH 16
/* 连接调度间隔12.5ms,单位125us */
#define SLE_CONN_INTV_MIN_DEFAULT 0x64
/* 连接调度间隔12.5ms,单位125us */
#define SLE_CONN_INTV_MAX_DEFAULT 0x64
/* 连接调度间隔25ms,单位125us */
#define SLE_ADV_INTERVAL_MIN_DEFAULT 0xC8
/* 连接调度间隔25ms,单位125us */
#define SLE_ADV_INTERVAL_MAX_DEFAULT 0xC8
/* 超时时间5000ms,单位10ms */
#define SLE_CONN_SUPERVISION_TIMEOUT_DEFAULT 0x1F4
/* 超时时间4990ms,单位10ms */
#define SLE_CONN_MAX_LATENCY 0x1F3
/* 广播发送功率 */
#define SLE_ADV_TX_POWER 10
/* 广播ID */
#define SLE_ADV_HANDLE_DEFAULT 1
/* 最大广播数据长度 */
#define SLE_ADV_DATA_LEN_MAX 251
/* 广播名称 */
static uint8_t sle_local_name[NAME_MAX_LENGTH] = "lamonce_test"; // 广播名称
#define SLE_SERVER_INIT_DELAY_MS 1000 // 广播初始化延时
#define sample_at_log_print(fmt, args...) osal_printk(fmt, ##args) // 日志打印宏
#define SLE_UART_SERVER_LOG "[sle uart server]" // 日志前缀// 设置广播设备名称,也是本地名称
static uint16_t sle_set_adv_local_name(uint8_t *adv_data, uint16_t max_len)
{errno_t ret;uint8_t index = 0;uint8_t *local_name = sle_local_name; // 赋值本地名称uint8_t local_name_len = sizeof(sle_local_name) - 1; // 不包括结束符sample_at_log_print("%s local_name_len = %d\r\n", SLE_UART_SERVER_LOG, local_name_len); // 日志sample_at_log_print("%s local_name: ", SLE_UART_SERVER_LOG);for (uint8_t i = 0; i < local_name_len; i++){sample_at_log_print("0x%02x ", local_name[i]);}sample_at_log_print("\r\n");adv_data[index++] = local_name_len + 1; // 长度+1adv_data[index++] = SLE_ADV_DATA_TYPE_COMPLETE_LOCAL_NAME; // 数据类型ret = memcpy_s(&adv_data[index], max_len - index, local_name, local_name_len); // 拷贝本地名称if (ret != EOK){sample_at_log_print("%s memcpy fail\r\n", SLE_UART_SERVER_LOG);return 0;}return (uint16_t)index + local_name_len;
}static uint16_t sle_set_adv_data(uint8_t *adv_data)
{size_t len = 0;uint16_t idx = 0;errno_t ret = 0;len = sizeof(struct sle_adv_common_value);struct sle_adv_common_value adv_disc_level = {.length = len - 1,.type = SLE_ADV_DATA_TYPE_DISCOVERY_LEVEL,.value = SLE_ANNOUNCE_LEVEL_NORMAL,};ret = memcpy_s(&adv_data[idx], SLE_ADV_DATA_LEN_MAX - idx, &adv_disc_level, len);if (ret != EOK){sample_at_log_print("%s adv_disc_level memcpy fail\r\n", SLE_UART_SERVER_LOG);return 0;}idx += len;len = sizeof(struct sle_adv_common_value);struct sle_adv_common_value adv_access_mode = {.length = len - 1,.type = SLE_ADV_DATA_TYPE_ACCESS_MODE,.value = 0,};ret = memcpy_s(&adv_data[idx], SLE_ADV_DATA_LEN_MAX - idx, &adv_access_mode, len);if (ret != EOK){sample_at_log_print("%s adv_access_mode memcpy fail\r\n", SLE_UART_SERVER_LOG);return 0;}idx += len;return idx;
}// 设置扫描响应数据
static uint16_t sle_set_scan_response_data(uint8_t *scan_rsp_data)
{uint16_t idx = 0;errno_t ret;size_t scan_rsp_data_len = sizeof(struct sle_adv_common_value);struct sle_adv_common_value tx_power_level = {.length = scan_rsp_data_len - 1,.type = SLE_ADV_DATA_TYPE_TX_POWER_LEVEL,.value = SLE_ADV_TX_POWER,};ret = memcpy_s(scan_rsp_data, SLE_ADV_DATA_LEN_MAX, &tx_power_level, scan_rsp_data_len);if (ret != EOK){sample_at_log_print("%s sle scan response data memcpy fail\r\n", SLE_UART_SERVER_LOG);return 0;}idx += scan_rsp_data_len;/* set local name */idx += sle_set_adv_local_name(&scan_rsp_data[idx], SLE_ADV_DATA_LEN_MAX - idx);return idx;
}// 设置广播参数
static int sle_set_default_announce_param(void)
{errno_t ret;sle_announce_param_t param = {0};uint8_t index;unsigned char local_addr[SLE_ADDR_LEN] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};param.announce_mode = SLE_ANNOUNCE_MODE_CONNECTABLE_SCANABLE;param.announce_handle = SLE_ADV_HANDLE_DEFAULT;param.announce_gt_role = SLE_ANNOUNCE_ROLE_T_CAN_NEGO;param.announce_level = SLE_ANNOUNCE_LEVEL_NORMAL;param.announce_channel_map = SLE_ADV_CHANNEL_MAP_DEFAULT;param.announce_interval_min = SLE_ADV_INTERVAL_MIN_DEFAULT;param.announce_interval_max = SLE_ADV_INTERVAL_MAX_DEFAULT;param.conn_interval_min = SLE_CONN_INTV_MIN_DEFAULT;param.conn_interval_max = SLE_CONN_INTV_MAX_DEFAULT;param.conn_max_latency = SLE_CONN_MAX_LATENCY;param.conn_supervision_timeout = SLE_CONN_SUPERVISION_TIMEOUT_DEFAULT;param.announce_tx_power = 18;param.own_addr.type = 0;ret = memcpy_s(param.own_addr.addr, SLE_ADDR_LEN, local_addr, SLE_ADDR_LEN);if (ret != EOK){sample_at_log_print("%s sle_set_default_announce_param data memcpy fail\r\n", SLE_UART_SERVER_LOG);return 0;}sample_at_log_print("%s sle_uart_local addr: ", SLE_UART_SERVER_LOG);for (index = 0; index < SLE_ADDR_LEN; index++){sample_at_log_print("0x%02x ", param.own_addr.addr[index]);}sample_at_log_print("\r\n");return sle_set_announce_param(param.announce_handle, ¶m);
}// 设置默认广播数据
static int sle_set_default_announce_data(void)
{errcode_t ret;uint8_t announce_data_len = 0;uint8_t seek_data_len = 0;sle_announce_data_t data = {0};uint8_t adv_handle = SLE_ADV_HANDLE_DEFAULT;uint8_t announce_data[SLE_ADV_DATA_LEN_MAX] = {0};uint8_t seek_rsp_data[SLE_ADV_DATA_LEN_MAX] = {0};uint8_t data_index = 0;announce_data_len = sle_set_adv_data(announce_data);data.announce_data = announce_data;data.announce_data_len = announce_data_len;sample_at_log_print("%s data.announce_data_len = %d\r\n", SLE_UART_SERVER_LOG, data.announce_data_len);sample_at_log_print("%s data.announce_data: ", SLE_UART_SERVER_LOG);for (data_index = 0; data_index < data.announce_data_len; data_index++){sample_at_log_print("0x%02x ", data.announce_data[data_index]);}sample_at_log_print("\r\n");seek_data_len = sle_set_scan_response_data(seek_rsp_data);data.seek_rsp_data = seek_rsp_data;data.seek_rsp_data_len = seek_data_len;sample_at_log_print("%s data.seek_rsp_data_len = %d\r\n", SLE_UART_SERVER_LOG, data.seek_rsp_data_len);sample_at_log_print("%s data.seek_rsp_data: ", SLE_UART_SERVER_LOG);for (data_index = 0; data_index < data.seek_rsp_data_len; data_index++){sample_at_log_print("0x%02x ", data.seek_rsp_data[data_index]);}sample_at_log_print("\r\n");ret = sle_set_announce_data(adv_handle, &data);if (ret == ERRCODE_SLE_SUCCESS){sample_at_log_print("%s set announce data success.\r\n", SLE_UART_SERVER_LOG);}else{sample_at_log_print("%s set adv param fail.\r\n", SLE_UART_SERVER_LOG);}return ERRCODE_SLE_SUCCESS;
}// 广播启动回调
static void sle_announce_enable_cbk(uint32_t announce_id, errcode_t status)
{sample_at_log_print("%s sle announce enable callback id:%02x, state:%x\r\n", SLE_UART_SERVER_LOG, announce_id,status);
}// 广播停止回调
static void sle_announce_disable_cbk(uint32_t announce_id, errcode_t status)
{sample_at_log_print("%s sle announce disable callback id:%02x, state:%x\r\n", SLE_UART_SERVER_LOG, announce_id,status);
}// 广播终止回调
static void sle_announce_terminal_cbk(uint32_t announce_id)
{sample_at_log_print("%s sle announce terminal callback id:%02x\r\n", SLE_UART_SERVER_LOG, announce_id);
}// 注册广播回调函数
errcode_t sle_uart_announce_register_cbks(void)
{errcode_t ret = 0;sle_announce_seek_callbacks_t seek_cbks = {0};seek_cbks.announce_enable_cb = sle_announce_enable_cbk;seek_cbks.announce_disable_cb = sle_announce_disable_cbk;seek_cbks.announce_terminal_cb = sle_announce_terminal_cbk;ret = sle_announce_seek_register_callbacks(&seek_cbks);if (ret != ERRCODE_SLE_SUCCESS){sample_at_log_print("%s sle_uart_announce_register_cbks,register_callbacks fail :%x\r\n",SLE_UART_SERVER_LOG, ret);return ret;}return ERRCODE_SLE_SUCCESS;
}// 初始化广播
errcode_t sle_uart_server_adv_init(void)
{errcode_t ret;sle_set_default_announce_param();sle_set_default_announce_data();ret = sle_start_announce(SLE_ADV_HANDLE_DEFAULT);sample_at_log_print("%s sle_uart_server_adv_init,sle_start_announce devise name :%s\r\n",SLE_UART_SERVER_LOG, sle_local_name); if (ret != ERRCODE_SLE_SUCCESS){sample_at_log_print("%s sle_uart_server_adv_init,sle_start_announce fail :%x\r\n", SLE_UART_SERVER_LOG, ret);return ret;}return ERRCODE_SLE_SUCCESS;
}
Client
sle_uart_client. c
/*** Copyright (c) HiSilicon (Shanghai) Technologies Co., Ltd. 2023-2023. All rights reserved.** Description: SLE uart sample of client. \n** History: \n* 2023-04-03, Create file. \n* * Code Comments: Written by Lamonce.* Last Modified: April 9, 2025 \n** Introduction:* This file implements the client-side functionality for UART data transmission based on the SLE* low-latency communication protocol. The client is responsible for scanning, discovering, and* connecting to the server device. It implements the complete connection lifecycle including* device discovery, connection establishment, pairing, information exchange, and bidirectional* data transfer. The implementation showcases how to properly register and handle various callbacks* for scanning, connection state changes, pairing completion, and data exchange events.** 简介:* 本文件实现了基于 SLE 低延迟通信协议的 UART(串口)数据传输客户端功能。* 客户端负责扫描、发现并连接到服务器设备,实现了完整的连接生命周期,* 包括设备发现、建立连接、配对、信息交换和双向数据传输。* 代码展示了如何正确注册和处理各种回调函数,包括扫描、连接状态变化、* 配对完成和数据交换事件的处理方法。** DISCLAIMER:* This code is provided for reference and learning purposes only.* No warranty of correctness, completeness, or suitability for any purpose is provided.* Before using in production environments, please review and test thoroughly.** 免责声明:* 本文件中的代码及注释仅供学习和参考使用,不保证其在所有环境下的正确性和完整性。* 在实际项目中使用前,请根据具体需求进行适当的修改和测试。**/// 通用头文件
#include "common_def.h" // 常用函数定义
#include "soc_osal.h" // 操作系统抽象层
#include "securec.h" // 安全 C 库,用于替代标准 C 库中可能存在风险的函数
#include "product.h" // 产品相关定义
#include "bts_le_gap.h" // BLE GAP 相关定义#include "sle_device_discovery.h" // 设备发现相关定义
#include "sle_connection_manager.h" // SLE 连接管理
#include "sle_uart_client.h" // 头文件#define SLE_MTU_SIZE_DEFAULT 520 // 默认 MTU 大小
#define SLE_SEEK_INTERVAL_DEFAULT 100 // 默认扫描间隔
#define SLE_SEEK_WINDOW_DEFAULT 100 // 默认扫描窗口
#define UUID_16BIT_LEN 2 // 16 位 UUID 长度
#define UUID_128BIT_LEN 16 // 128 位 UUID 长度
#define SLE_UART_TASK_DELAY_MS 1000 // 任务延时
#define SLE_UART_WAIT_SLE_CORE_READY_MS 5000 // 等待 SLE 核心准备就绪
#define SLE_UART_RECV_CNT 1000
#define SLE_UART_LOW_LATENCY_2K 2000
#ifndef SLE_UART_SERVER_NAME
#define SLE_UART_SERVER_NAME "lamonce_test" // 默认 Server 名称
#endif
#define SLE_UART_CLIENT_LOG "[sle uart client]" // 日志前缀static ssapc_find_service_result_t g_sle_uart_find_service_result = {0};
static sle_announce_seek_callbacks_t g_sle_uart_seek_cbk = {0};
static sle_connection_callbacks_t g_sle_uart_connect_cbk = {0};
static ssapc_callbacks_t g_sle_uart_ssapc_cbk = {0};
static sle_addr_t g_sle_uart_remote_addr = {0};
ssapc_write_param_t g_sle_uart_send_param = {0};
uint16_t g_sle_uart_conn_id = 0;uint16_t get_g_sle_uart_conn_id(void)
{return g_sle_uart_conn_id;
}ssapc_write_param_t *get_g_sle_uart_send_param(void)
{return &g_sle_uart_send_param;
}// --------- SLE 扫描回调函数 --------// SLE Client 端开始扫描
void sle_uart_start_scan(void)
{sle_seek_param_t param = {0}; // 创建扫描参数结构体param.own_addr_type = 0; // 本端地址类型param.filter_duplicates = 0; // 重复过滤开关,0:关闭,1:开启param.seek_filter_policy = 0; // 扫描设备使用的过滤类型,见 sle_seek_filter_tparam.seek_phys = 1; // 扫描设备所使用的 PHY(物理层)的传输速度,见 sle_seek_phy_tparam.seek_type[0] = 1; // 扫描类型,见 sle_seek_type_tparam.seek_interval[0] = SLE_SEEK_INTERVAL_DEFAULT; // 扫描间隔,取值范围[0x0004, 0xFFFF],time = N * 0.125msparam.seek_window[0] = SLE_SEEK_WINDOW_DEFAULT; // 扫描窗口,取值范围[0x0004, 0xFFFF],time = N * 0.125mssle_set_seek_param(¶m); // 设置扫描参数sle_start_seek(); // 开始扫描
}// SLE Client 端使能回调
// 这个函数只能进入一次,否则会造成死循环
static void sle_uart_client_sample_sle_enable_cbk(errcode_t status)
{osal_printk("sle enable: %d.\r\n", status);sle_uart_client_init(sle_uart_notification_cb, sle_uart_indication_cb); // 初始化sle_uart_start_scan(); // 启动扫描
}// SLE Client 端开始扫描使能回调
static void sle_uart_client_sample_seek_enable_cbk(errcode_t status)
{if (status != 0){osal_printk("%s sle_uart_client_sample_seek_enable_cbk,status error\r\n", SLE_UART_CLIENT_LOG);}
}// SLE Client 端扫描结果回调
static void sle_uart_client_sample_seek_result_info_cbk(sle_seek_result_info_t *seek_result_data)
{osal_printk("%s sle uart scan data :%s\r\n", SLE_UART_CLIENT_LOG, seek_result_data->data);// 扫描结果为空if (seek_result_data == NULL){osal_printk("status error\r\n");}// 扫描结果非空// 判断扫描结果中的设备名是否和定义的 Server 名称匹配else if (strstr((const char *)seek_result_data->data, SLE_UART_SERVER_NAME) != NULL){// 若匹配,则保存扫描结果中的地址memcpy_s(&g_sle_uart_remote_addr, sizeof(sle_addr_t), &seek_result_data->addr, sizeof(sle_addr_t));sle_stop_seek(); // 停止扫描}
}// SLE Client 端扫描关闭回调
static void sle_uart_client_sample_seek_disable_cbk(errcode_t status)
{// 扫描关闭成功,但未找到目标设备if (status != 0){osal_printk("%s sle_uart_client_sample_seek_disable_cbk,status error = %x\r\n", SLE_UART_CLIENT_LOG, status);}// 找到目标设备else{// 通过设备地址连接目标设备// 连接前先删除之前的配对信息sle_remove_paired_remote_device(&g_sle_uart_remote_addr);// 连接目标设备sle_connect_remote_device(&g_sle_uart_remote_addr);}
}// SLE Client 端注册扫描回调函数
static void sle_uart_client_sample_seek_cbk_register(void)
{g_sle_uart_seek_cbk.sle_enable_cb = sle_uart_client_sample_sle_enable_cbk; // 使能回调g_sle_uart_seek_cbk.seek_enable_cb = sle_uart_client_sample_seek_enable_cbk; // 扫描使能回调g_sle_uart_seek_cbk.seek_result_cb = sle_uart_client_sample_seek_result_info_cbk; // 扫描结果回调g_sle_uart_seek_cbk.seek_disable_cb = sle_uart_client_sample_seek_disable_cbk; // 扫描关闭回调sle_announce_seek_register_callbacks(&g_sle_uart_seek_cbk); // 注册回调函数
}// -------- SLE 扫描回调函数结束 --------// -------- SLE 连接管理回调函数 --------/*** @brief SLE 连接状态改变回调函数** @param conn_id 连接 ID* @param addr 设备地址* @param conn_state 连接状态* @param pair_state 配对状态* @param disc_reason 断开原因** @attention ACB - Auto Connection Boost 自动连接增强** @retval None*/
static void sle_uart_client_sample_connect_state_changed_cbk(uint16_t conn_id, const sle_addr_t *addr,sle_acb_state_t conn_state, sle_pair_state_t pair_state,sle_disc_reason_t disc_reason)
{// 标记未使用的参数unused(addr);unused(pair_state);// 打印连接状态改变的日志osal_printk("%s conn state changed disc_reason:0x%x\r\n", SLE_UART_CLIENT_LOG, disc_reason);g_sle_uart_conn_id = conn_id;// 检查连接状态// 已连接if (conn_state == SLE_ACB_STATE_CONNECTED){osal_printk("%s SLE_ACB_STATE_CONNECTED\r\n", SLE_UART_CLIENT_LOG);// 检查配对状态,若未配对,则进行配对if (pair_state == SLE_PAIR_NONE){sle_pair_remote_device(&g_sle_uart_remote_addr);}osal_printk("%s sle_low_latency_rx_enable \r\n", SLE_UART_CLIENT_LOG);}// 未连接else if (conn_state == SLE_ACB_STATE_NONE){osal_printk("%s SLE_ACB_STATE_NONE\r\n", SLE_UART_CLIENT_LOG);}// 断开连接else if (conn_state == SLE_ACB_STATE_DISCONNECTED){osal_printk("%s SLE_ACB_STATE_DISCONNECTED\r\n", SLE_UART_CLIENT_LOG);sle_remove_paired_remote_device(&g_sle_uart_remote_addr); // 清除配对信息sle_uart_start_scan(); // 重新开始扫描}else{osal_printk("%s status error\r\n", SLE_UART_CLIENT_LOG);}
}/*** @brief 配对完成回调函数** @param conn_id 连接 ID* @param addr 设备地址* @param status 状态码*/
void sle_uart_client_sample_pair_complete_cbk(uint16_t conn_id, const sle_addr_t *addr, errcode_t status)
{osal_printk("%s pair complete conn_id:%d, addr:%02x***%02x%02x\n", SLE_UART_CLIENT_LOG, conn_id,addr->addr[0], addr->addr[4], addr->addr[5]);if (status == 0){ssap_exchange_info_t info = {0}; // 创建交换信息结构体info.mtu_size = SLE_MTU_SIZE_DEFAULT; // MTU 大小info.version = 1; // 信息版本号ssapc_exchange_info_req(0, g_sle_uart_conn_id, &info); // 与 Server 端交换信息}
}// SLE Client 端注册连接管理回调函数
static void sle_uart_client_sample_connect_cbk_register(void)
{g_sle_uart_connect_cbk.connect_state_changed_cb = sle_uart_client_sample_connect_state_changed_cbk; // 连接状态改变回调g_sle_uart_connect_cbk.pair_complete_cb = sle_uart_client_sample_pair_complete_cbk; // 配对完成回调sle_connection_register_callbacks(&g_sle_uart_connect_cbk); // 注册回调
}// -------- SLE 连接管理回调函数结束 ----// -------- SLE SSAPC 回调函数 --------/*** @brief 连接信息交换回调函数** @param client_id 设备 ID* @param conn_id 连接 ID* @param param 交换信息参数* @param status 状态码*/
static void sle_uart_client_sample_exchange_info_cbk(uint8_t client_id, uint16_t conn_id, ssap_exchange_info_t *param,errcode_t status)
{osal_printk("%s exchange_info_cbk,pair complete client id:%d status:%d\r\n",SLE_UART_CLIENT_LOG, client_id, status);osal_printk("%s exchange mtu, mtu size: %d, version: %d.\r\n", SLE_UART_CLIENT_LOG,param->mtu_size, param->version);ssapc_find_structure_param_t find_param = {0}; // 创建查找参数结构体find_param.type = SSAP_FIND_TYPE_PROPERTY; // 查找类型:属性find_param.start_hdl = 1; // 开始句柄find_param.end_hdl = 0xFFFF; // 结束句柄ssapc_find_structure(0, conn_id, &find_param); // 查找(服务、特征、描述符)
}// 查找服务回调函数
static void sle_uart_client_sample_find_structure_cbk(uint8_t client_id, uint16_t conn_id,ssapc_find_service_result_t *service,errcode_t status)
{osal_printk("%s find structure cbk client: %d conn_id:%d status: %d \r\n", SLE_UART_CLIENT_LOG,client_id, conn_id, status);osal_printk("%s find structure start_hdl:[0x%02x], end_hdl:[0x%02x], uuid len:%d\r\n", SLE_UART_CLIENT_LOG,service->start_hdl, service->end_hdl, service->uuid.len);g_sle_uart_find_service_result.start_hdl = service->start_hdl; // 保存服务的开始句柄g_sle_uart_find_service_result.end_hdl = service->end_hdl; // 保存服务的结束句柄memcpy_s(&g_sle_uart_find_service_result.uuid, sizeof(sle_uuid_t), &service->uuid, sizeof(sle_uuid_t)); // 保存服务的 UUID
}// 查找属性回调函数
static void sle_uart_client_sample_find_property_cbk(uint8_t client_id, uint16_t conn_id,ssapc_find_property_result_t *property, errcode_t status)
{osal_printk("%s sle_uart_client_sample_find_property_cbk, client id: %d, conn id: %d, operate ind: %d, ""descriptors count: %d status:%d property->handle %d\r\n",SLE_UART_CLIENT_LOG,client_id, conn_id, property->operate_indication,property->descriptors_count, status, property->handle);g_sle_uart_send_param.handle = property->handle; // 保存属性句柄g_sle_uart_send_param.type = SSAP_PROPERTY_TYPE_VALUE; // 属性类型
}// 查找结构完成回调函数
static void sle_uart_client_sample_find_structure_cmp_cbk(uint8_t client_id, uint16_t conn_id,ssapc_find_structure_result_t *structure_result,errcode_t status)
{unused(conn_id);osal_printk("%s sle_uart_client_sample_find_structure_cmp_cbk,client id:%d status:%d type:%d uuid len:%d \r\n",SLE_UART_CLIENT_LOG, client_id, status, structure_result->type, structure_result->uuid.len);
}// 写请求回调函数
static void sle_uart_client_sample_write_cfm_cb(uint8_t client_id, uint16_t conn_id,ssapc_write_result_t *write_result, errcode_t status)
{osal_printk("%s sle_uart_client_sample_write_cfm_cb, conn_id:%d client id:%d status:%d handle:%02x type:%02x\r\n",SLE_UART_CLIENT_LOG, conn_id, client_id, status, write_result->handle, write_result->type);
}// SSAPC 回调函数注册
static void sle_uart_client_sample_ssapc_cbk_register(ssapc_notification_callback notification_cb,ssapc_notification_callback indication_cb)
{g_sle_uart_ssapc_cbk.exchange_info_cb = sle_uart_client_sample_exchange_info_cbk;g_sle_uart_ssapc_cbk.find_structure_cb = sle_uart_client_sample_find_structure_cbk;g_sle_uart_ssapc_cbk.ssapc_find_property_cbk = sle_uart_client_sample_find_property_cbk;g_sle_uart_ssapc_cbk.find_structure_cmp_cb = sle_uart_client_sample_find_structure_cmp_cbk;g_sle_uart_ssapc_cbk.write_cfm_cb = sle_uart_client_sample_write_cfm_cb;g_sle_uart_ssapc_cbk.notification_cb = notification_cb;g_sle_uart_ssapc_cbk.indication_cb = indication_cb;ssapc_register_callbacks(&g_sle_uart_ssapc_cbk);
}// -------- SLE SSAPC 回调函数结束 -----// -------- SLE UART Client 端初始化 --------
void sle_uart_client_init(ssapc_notification_callback notification_cb, ssapc_indication_callback indication_cb)
{(void)osal_msleep(5000); /* 延时5s,等待SLE初始化完毕 */osal_printk("[SLE Client] try enable.\r\n");sle_uart_client_sample_seek_cbk_register(); // 注册扫描回调函数sle_uart_client_sample_connect_cbk_register(); // 注册连接管理回调函数sle_uart_client_sample_ssapc_cbk_register(notification_cb, indication_cb); // 注册 SSAPC 回调函数// 使能 SLE 协议栈if (enable_sle() != ERRCODE_SUCC){osal_printk("[SLE Client] sle enbale fail !\r\n");}
}
Solution
/*** Copyright (c) HiSilicon (Shanghai) Technologies Co., Ltd. 2023-2023. All rights reserved.** Description: SLE UART Sample Source. \n** History: \n* 2023-07-17, Create file. \n** Code Comments: Written by Lamonce.* Last Modified: April 9, 2025 \n** Introduction:* This file implements a UART data transmission example based on the SLE* low-latency communication protocol. The code supports two working modes: Server and Client,* selected through macro definitions. The Server broadcasts its existence and accepts Client* connection requests, handling data exchange; the Client discovers, connects to the Server,* and performs data transmission. This example demonstrates how to configure UART,* initialize the SLE protocol stack, establish connections, and implement bidirectional* data transfer.** 简介:* 本文件实现了基于 SLE 低延迟通信协议的UART(串口)数据传输示例。* 代码支持两种工作模式:Server 端和 Client 端,通过宏定义进行选择。* Server 端负责广播自身存在并接收 Client 连接请求,处理数据交换;* Client 端负责发现、连接 Server 并完成数据传输。* 该示例展示了如何配置串口、初始化 SLE 协议栈、建立连接并实现双向数据传输。** DISCLAIMER:* This code is provided for reference and learning purposes only.* No warranty of correctness, completeness, or suitability for any purpose is provided.* Before using in production environments, please review and test thoroughly.** 免责声明:* 本文件中的代码及注释仅供学习和参考使用,不保证其在所有环境下的正确性和完整性。* 在实际项目中使用前,请根据具体需求进行适当的修改和测试。**/#include "common_def.h" // 常用函数定义
#include "soc_osal.h" // 硬件抽象层
#include "app_init.h" // 应用初始化
#include "pinctrl.h" // 引脚控制
#include "uart.h" // 串口控制
// #include "pm_clock.h" // 时钟控制
#include "sle_low_latency.h" // 低延迟通信框架
#include "gpio.h" // GPIO 控制// ---- 判断设备类型 ----#if defined(CONFIG_SAMPLE_SUPPORT_SLE_UART_SERVER_SOLUTION) // Server 端测试
#include "securec.h" // 安全函数库,用于替代标准 C 库中不安全的函数
#include "sle_uart_server.h" // Server 端头文件
#include "sle_uart_server_adv.h" // Server 端广播相关头文件
#include "sle_device_discovery.h" // 设备发现相关头文件
#include "sle_errcode.h" // 错误码定义#elif defined(CONFIG_SAMPLE_SUPPORT_SLE_UART_CLIENT_SOLUTION) // Client 端测试
#define SLE_UART_TASK_STACK_SIZE 0x600 // 任务栈大小
#include "sle_connection_manager.h" // 连接管理
#include "sle_ssap_client.h" // ssap 客户端
#include "sle_uart_client.h" // Client 端头文件
#endif /* CONFIG_SAMPLE_SUPPORT_SLE_UART_CLIENT */// ---- 设备类型判断结束 ----// ---- 串口配置 ----#define SLE_UART_TASK_PRIO 28 // 任务优先级
#define SLE_UART_TASK_DURATION_MS 2000 // 任务休眠时间
#define SLE_UART_BAUDRATE 115200 // 串口波特率
#define SLE_UART_TRANSFER_SIZE 512 // 串口传输缓冲区大小static uint8_t g_app_uart_rx_buff[SLE_UART_TRANSFER_SIZE] = {0}; // 串口接收缓冲区// UART 缓冲区配置
static uart_buffer_config_t g_app_uart_buffer_config = {.rx_buffer = g_app_uart_rx_buff,.rx_buffer_size = SLE_UART_TRANSFER_SIZE};// UART 引脚配置
static void uart_init_pin(void)
{// 判断当前使用的串口,串口号定义见 Kconfigif (CONFIG_SLE_UART_BUS == 0){// 引脚模式配置,引脚号定义见 Kconfig// 引脚连接方式参考:// https://www.bearpi.cn/core_board/bearpi/pico/h3863/hardware/%E5%8E%9F%E7%90%86%E5%9B%BE.html#%F0%9F%93%9C-%E5%8E%9F%E7%90%86%E5%9B%BE// 引脚模式参考:// https://gitee.com/HiSpark/fbb_ws63/blob/master/docs/board/IO%E5%A4%8D%E7%94%A8%E5%85%B3%E7%B3%BB.mduapi_pin_set_mode(CONFIG_UART_TXD_PIN, PIN_MODE_1);uapi_pin_set_mode(CONFIG_UART_RXD_PIN, PIN_MODE_1);}else if (CONFIG_SLE_UART_BUS == 1){uapi_pin_set_mode(CONFIG_UART_TXD_PIN, PIN_MODE_1);uapi_pin_set_mode(CONFIG_UART_RXD_PIN, PIN_MODE_1);}
}// UART 参数配置
static void uart_init_config(void)
{// 数据帧格式配置uart_attr_t attr = {.baud_rate = SLE_UART_BAUDRATE, // 波特率.data_bits = UART_DATA_BIT_8, // 数据位长度.stop_bits = UART_STOP_BIT_1, // 停止位长度.parity = UART_PARITY_NONE}; // 校验位长度// 引脚定义,在 ws63 上,txd 和 rxd 不可自定义,应参考硬件设计// 此处引脚定义无效uart_pin_config_t pin_config = {.tx_pin = CONFIG_UART_TXD_PIN,.rx_pin = CONFIG_UART_RXD_PIN,.cts_pin = PIN_NONE,.rts_pin = PIN_NONE};uapi_uart_deinit(CONFIG_SLE_UART_BUS); // 反初始化串口uapi_uart_init(CONFIG_SLE_UART_BUS, &pin_config, &attr, NULL, &g_app_uart_buffer_config); // 初始化串口
}// ---- 串口配置结束 ----// ---- 任务函数 ----
// 根据宏定义判断当前设备类型,启用对应的任务函数// Server 端
#if defined(CONFIG_SAMPLE_SUPPORT_SLE_UART_SERVER_SOLUTION)
#define SLE_UART_SERVER_DELAY_COUNT 5#define SLE_UART_TASK_STACK_SIZE 0x1200 // 任务栈大小
#define SLE_ADV_HANDLE_DEFAULT 1 // 广播句柄
#define SLE_UART_SERVER_MSG_QUEUE_LEN 5 // 消息队列长度
#define SLE_UART_SERVER_MSG_QUEUE_MAX_SIZE 32 // 消息节点大小
#define SLE_UART_SERVER_QUEUE_DELAY 0xFFFFFFFF // 消息队列延时,此处设置为最大值
#define SLE_UART_SERVER_BUFF_MAX_SIZE 800unsigned long g_sle_uart_server_msgqueue_id; // 消息队列句柄
#define SLE_UART_SERVER_LOG "[sle uart server]" // 日志前缀// 读请求回调函数
static void ssaps_server_read_request_cbk(uint8_t server_id, uint16_t conn_id, ssaps_req_read_cb_t *read_cb_para,errcode_t status)
{osal_printk("%s ssaps read request cbk callback server_id:%x, conn_id:%x, handle:%x, status:%x\r\n",SLE_UART_SERVER_LOG, server_id, conn_id, read_cb_para->handle, status);
}// 写请求回调函数
static void ssaps_server_write_request_cbk(uint8_t server_id, uint16_t conn_id, ssaps_req_write_cb_t *write_cb_para,errcode_t status)
{osal_printk("%s ssaps write request callback cbk server_id:%x, conn_id:%x, handle:%x, status:%x\r\n",SLE_UART_SERVER_LOG, server_id, conn_id, write_cb_para->handle, status);// 判断写入数据的长度和内容if ((write_cb_para->length > 0) && write_cb_para->value){// 打印接收的数据osal_printk("\n sle uart received data : %s\r\n", write_cb_para->value);uapi_uart_write(CONFIG_SLE_UART_BUS, (uint8_t *)write_cb_para->value, write_cb_para->length, 0);}
}/*** 处理学生数据,将"姓名,学号,年龄"格式转换为"name:姓名,num:学号,age:年龄"** @param input 输入数据缓冲区* @param input_length 输入数据长度* @param output 输出数据缓冲区* @param output_length 输出数据长度指针,函数返回时会更新为实际长度* @return 处理是否成功*/
static bool process_student_data(const uint8_t *input, uint16_t input_length, uint8_t *output, uint16_t *output_length)
{if (input == NULL || output == NULL || output_length == NULL || input_length == 0){return false;}// 最大可接受的输出长度const uint16_t max_output_length = SLE_UART_TRANSFER_SIZE;// 创建变量uint16_t i = 0;uint16_t name_start = 0;uint16_t name_end = 0;uint16_t number_start = 0;uint16_t number_end = 0;uint16_t age_start = 0;uint16_t commas_found = 0;// 查找逗号位置以确定字段边界for (i = 0; i < input_length; i++){if (input[i] == ','){commas_found++;if (commas_found == 1){name_end = i;number_start = i + 1;}else if (commas_found == 2){number_end = i;age_start = i + 1;break;}}}// 检查是否找到两个逗号if (commas_found < 2){osal_printk("Invalid data format, requires two commas to separate three fields\r\n");return false;}// 计算字段长度uint16_t name_len = name_end - name_start;uint16_t number_len = number_end - number_start;uint16_t age_len = input_length - age_start;// 计算需要的输出缓冲区大小(加上前缀和分隔符)uint16_t required_size = 5 + name_len + 5 + number_len + 5 + age_len; // "name:" + name + ",num:" + number + ",age:" + ageif (required_size > max_output_length){osal_printk("Processed data length exceeds buffer size\r\n");return false;}// 构建输出字符串uint16_t pos = 0;// 添加 "name:" 前缀if (memcpy_s(&output[pos], max_output_length - pos, "name:", 5) != EOK){return false;}pos += 5;// 添加姓名if (memcpy_s(&output[pos], max_output_length - pos, &input[name_start], name_len) != EOK){return false;}pos += name_len;// 添加 ",num:" 前缀if (memcpy_s(&output[pos], max_output_length - pos, ",num:", 5) != EOK){return false;}pos += 5;// 添加学号if (memcpy_s(&output[pos], max_output_length - pos, &input[number_start], number_len) != EOK){return false;}pos += number_len;// 添加 ",age:" 前缀if (memcpy_s(&output[pos], max_output_length - pos, ",age:", 5) != EOK){return false;}pos += 5;// 添加年龄if (memcpy_s(&output[pos], max_output_length - pos, &input[age_start], age_len) != EOK){return false;}pos += age_len;*output_length = pos;return true;
}// UART 接收中断处理函数
static void sle_uart_server_read_int_handler(const void *buffer, uint16_t length, bool error)
{unused(error);// 检查 Client 端是否连接if (sle_uart_client_is_connected()){// 处理数据uint8_t processed_data[SLE_UART_TRANSFER_SIZE] = {0};uint16_t processed_length = 0;if (process_student_data((const uint8_t *)buffer, length, processed_data, &processed_length)){// 发送处理后的数据到 Client 端osal_printk("%s Data processing successful, sending processed data\r\n", SLE_UART_SERVER_LOG);sle_uart_server_send_report_by_handle(processed_data, processed_length);}else{// 处理失败,发送原始数据osal_printk("%s Data processing failed, sending original data\r\n", SLE_UART_SERVER_LOG);sle_uart_server_send_report_by_handle(buffer, length);}}else{osal_printk("%s sle client is not connected! \r\n", SLE_UART_SERVER_LOG);}
}// 创建消息队列
static void sle_uart_server_create_msgqueue(void)
{if (osal_msg_queue_create("sle_uart_server_msgqueue", // 队列名称,保留SLE_UART_SERVER_MSG_QUEUE_LEN, // 队列长度(unsigned long *)&g_sle_uart_server_msgqueue_id, // 成功创建的队列控制结构的 ID0, // 队列模式,保留SLE_UART_SERVER_MSG_QUEUE_MAX_SIZE) != OSAL_SUCCESS) // 消息节点大小{osal_printk("^%s sle_uart_server_create_msgqueue message queue create failed!\n", SLE_UART_SERVER_LOG);}
}// 删除消息队列
static void sle_uart_server_delete_msgqueue(void)
{osal_msg_queue_delete(g_sle_uart_server_msgqueue_id);
}// 写入消息队列
static void sle_uart_server_write_msgqueue(uint8_t *buffer_addr, uint16_t buffer_size)
{osal_msg_queue_write_copy(g_sle_uart_server_msgqueue_id, (void *)buffer_addr,(uint32_t)buffer_size, 0);
}// 从消息队列读取数据
static int32_t sle_uart_server_receive_msgqueue(uint8_t *buffer_addr, uint32_t *buffer_size)
{return osal_msg_queue_read_copy(g_sle_uart_server_msgqueue_id, (void *)buffer_addr,buffer_size, SLE_UART_SERVER_QUEUE_DELAY);
}// 初始化接收缓冲区,实际操作为清空缓冲区(全部置为 0)
static void sle_uart_server_rx_buf_init(uint8_t *buffer_addr, uint32_t *buffer_size)
{*buffer_size = SLE_UART_SERVER_MSG_QUEUE_MAX_SIZE;(void)memset_s(buffer_addr, *buffer_size, 0, *buffer_size);
}// 任务函数
static void *sle_uart_server_task(const char *arg)
{unused(arg);uint8_t rx_buf[SLE_UART_SERVER_MSG_QUEUE_MAX_SIZE] = {0}; // 定义接收缓冲区uint32_t rx_length = SLE_UART_SERVER_MSG_QUEUE_MAX_SIZE; // 接收缓冲区长度uint8_t sle_connect_state[] = "sle_dis_connect"; // 连接状态sle_uart_server_create_msgqueue(); // 创建消息队列sle_uart_server_register_msg(sle_uart_server_write_msgqueue); // 注册消息队列sle_uart_server_init(ssaps_server_read_request_cbk, ssaps_server_write_request_cbk); // 初始化 Server 端// 这是一个高度抽象的函数,将 SLE Server 的多种回调函数的注册、启动广播等操作进行集成/* UART pinmux. */// 初始化引脚uart_init_pin();/* UART init config. */// 初始化串口配置uart_init_config();// 反注册串口回调函数uapi_uart_unregister_rx_callback(CONFIG_SLE_UART_BUS);// 注册接收回调函数,这个回调函数会根据触发条件和Size触发errcode_t ret = uapi_uart_register_rx_callback(CONFIG_SLE_UART_BUS, // 串口号UART_RX_CONDITION_FULL_OR_IDLE, // 触发条件,参见 uart_rx_condition_t,如果接收缓存已满,或者接收的数据量到达指定的数据长度,就触发数据接收回调1, // 如果触发条件涉及到数据长度,这个参数就表示需要的数据长度sle_uart_server_read_int_handler); // 接收数据的回调函数// 检查注册结果if (ret != ERRCODE_SUCC){osal_printk("%s Register uart callback fail.[%x]\r\n", SLE_UART_SERVER_LOG, ret);return NULL;}// 进入死循环while (1){// 清空接收缓冲区sle_uart_server_rx_buf_init(rx_buf, &rx_length);// 接收数据sle_uart_server_receive_msgqueue(rx_buf, &rx_length);// 检查接收数据是否为定义的未连接状态,若未连接,则开始广播if (strncmp((const char *)rx_buf, (const char *)sle_connect_state, sizeof(sle_connect_state)) == 0){ret = sle_start_announce(SLE_ADV_HANDLE_DEFAULT);if (ret != ERRCODE_SLE_SUCCESS){osal_printk("%s sle_connect_state_changed_cbk,sle_start_announce fail :%02x\r\n",SLE_UART_SERVER_LOG, ret);}}osal_msleep(SLE_UART_TASK_DURATION_MS); // 休眠一段时间,然后再次检查连接状态}// 删除消息队列sle_uart_server_delete_msgqueue();return NULL;
}// Client 端
#elif defined(CONFIG_SAMPLE_SUPPORT_SLE_UART_CLIENT_SOLUTION)#define BLINKY_PIN 2// GPIO 初始化
void blinkyInit(void)
{// GPIO 设置uapi_pin_set_mode(BLINKY_PIN, PIN_MODE_0); // 设置引脚模式为 GPIOuapi_gpio_set_dir(BLINKY_PIN, GPIO_DIRECTION_OUTPUT); // 设置引脚方向为输出uapi_gpio_set_val(BLINKY_PIN, GPIO_LEVEL_LOW); // 设置引脚输出低电平
}/** Notification and Indication* 1. Notification: Client 端不需要响应,Server 端发送数据后直接接收* 2. Indication: Client 端需要响应,Server 端发送数据后需要等待 Client 端的响应*/// 通知回调函数
void sle_uart_notification_cb(uint8_t client_id, uint16_t conn_id, ssapc_handle_value_t *data,errcode_t status)
{unused(client_id);unused(conn_id);unused(status);osal_printk("\n sle uart recived data : %s\r\n", data->data);// 判断最后一个字符的ASCII值的奇偶性if (data->data_len > 0){// 获取最后一个有效字符uint8_t last_char = data->data[data->data_len - 1];// 判断ASCII值是奇数还是偶数if (last_char % 2 == 1){// ASCII值为奇数,点亮LEDosal_printk("Last character ASCII value %d is odd, turning LED ON\r\n", last_char);uapi_gpio_set_val(BLINKY_PIN, GPIO_LEVEL_HIGH);}else{// ASCII值为偶数,熄灭LEDosal_printk("Last character ASCII value %d is even, turning LED OFF\r\n", last_char);uapi_gpio_set_val(BLINKY_PIN, GPIO_LEVEL_LOW);}uapi_uart_write(CONFIG_SLE_UART_BUS, (uint8_t *)(data->data), data->data_len, 0);}
}// 指示回调函数
void sle_uart_indication_cb(uint8_t client_id, uint16_t conn_id, ssapc_handle_value_t *data,errcode_t status)
{unused(client_id);unused(conn_id);unused(status);osal_printk("\n sle uart recived data : %s\r\n", data->data);uapi_uart_write(CONFIG_SLE_UART_BUS, (uint8_t *)(data->data), data->data_len, 0);
}// UART 接收回调函数
static void sle_uart_client_read_int_handler(const void *buffer, uint16_t length, bool error)
{unused(error);ssapc_write_param_t *sle_uart_send_param = get_g_sle_uart_send_param();uint16_t g_sle_uart_conn_id = get_g_sle_uart_conn_id();sle_uart_send_param->data_len = length;sle_uart_send_param->data = (uint8_t *)buffer;ssapc_write_req(0, g_sle_uart_conn_id, sle_uart_send_param);
}// Client 端任务函数
static void *sle_uart_client_task(const char *arg)
{unused(arg);/* UART pinmux. */uart_init_pin();/* UART init config. */uart_init_config();uapi_uart_unregister_rx_callback(CONFIG_SLE_UART_BUS);// 注册接收回调函数,这个回调函数会根据触发条件和Size触发errcode_t ret = uapi_uart_register_rx_callback(CONFIG_SLE_UART_BUS,UART_RX_CONDITION_FULL_OR_IDLE,1, sle_uart_client_read_int_handler);// 初始化 Client 端// 这是一个高度抽象的函数,将 SLE Client 的多种回调函数的注册进行集成sle_uart_client_init(sle_uart_notification_cb, sle_uart_indication_cb);if (ret != ERRCODE_SUCC){osal_printk("Register uart callback fail.");return NULL;}return NULL;
}
#endif /* CONFIG_SAMPLE_SUPPORT_SLE_UART_CLIENT */// ---- 任务函数结束 ----// ---- 任务入口函数 ----
static void sle_uart_entry(void)
{osal_task *task_handle = NULL; // 任务句柄osal_kthread_lock(); // 锁任务调度// 通过宏定义判断当前设备类型,创建对应的任务,返回任务句柄
#if defined(CONFIG_SAMPLE_SUPPORT_SLE_UART_SERVER_SOLUTION) // Server 端测试task_handle = osal_kthread_create((osal_kthread_handler)sle_uart_server_task, 0, "SLEUartServerTask",SLE_UART_TASK_STACK_SIZE);#elif defined(CONFIG_SAMPLE_SUPPORT_SLE_UART_CLIENT_SOLUTION) // Client 端测试task_handle = osal_kthread_create((osal_kthread_handler)sle_uart_client_task, 0, "SLEUartDongleTask",SLE_UART_TASK_STACK_SIZE);blinkyInit();
#endif /* CONFIG_SAMPLE_SUPPORT_SLE_UART_CLIENT */if (task_handle != NULL) // 判断任务是否创建成功{osal_kthread_set_priority(task_handle, SLE_UART_TASK_PRIO); // 设置任务优先级}osal_kthread_unlock(); // 解锁任务调度
}
// ---- 任务入口函数结束 ----/* Run the sle_uart_entry. */
// 程序入口函数
app_run(sle_uart_entry);