硬件设计
设计一款支持星闪(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)
{
// 判断当前使用的串口,串口号定义见 Kconfig
if (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.md
uapi_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 800
unsigned 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, // 成功创建的队列控制结构的 ID
0, // 队列模式,保留
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 的长度为 2
encode2byte_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_callback
ssaps_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); // 设置服务 UUID
ret = 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 长度
// 复制应用 UUID
if (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); // 设置 UUID
ret = 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_callback
ssaps_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; // 长度+1
adv_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_t
param.seek_phys = 1; // 扫描设备所使用的 PHY(物理层)的传输速度,见 sle_seek_phy_t
param.seek_type[0] = 1; // 扫描类型,见 sle_seek_type_t
param.seek_interval[0] = SLE_SEEK_INTERVAL_DEFAULT; // 扫描间隔,取值范围[0x0004, 0xFFFF],time = N * 0.125ms
param.seek_window[0] = SLE_SEEK_WINDOW_DEFAULT; // 扫描窗口,取值范围[0x0004, 0xFFFF],time = N * 0.125ms
sle_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)
{
// 判断当前使用的串口,串口号定义见 Kconfig
if (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.md
uapi_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 800
unsigned 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:" + age
if (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, // 成功创建的队列控制结构的 ID
0, // 队列模式,保留
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); // 设置引脚模式为 GPIO
uapi_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值为奇数,点亮LED
osal_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值为偶数,熄灭LED
osal_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);