当前位置: 首页 > news >正文

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 软件介绍

软件功能详解:

  1. 原理图设计:符号库调用、电气规则检查(ERC)
  2. PCB 布局:层叠结构定义(本设计采用双层板)、布线规则设置
  3. 元件库管理:
    1. 基础元件:电阻(限流/分压)、电感(滤波)、电容(去耦)
    2. 芯片类:CH340G(引脚功能:TXD/RXD 串口通信)、AMS1117(输入/输出/接地引脚配置)
  4. 团队协作功能:支持多人实时编辑

创建元件及封装

BM-H63 自定义封装设计流程:

  1. 引脚定义提取

    1. 根据模块数据手册标注引脚(如 VCC、GND、UART_TX/RX)
    2. 数据手册链接:BM-H63 数据手册
    3. 模块尺寸图
      在这里插入图片描述
  2. 焊盘设计:

    1. 尺寸匹配
    2. 间距控制
  3. 符号库绑定:原理图符号与封装绑定,添加引脚电气属性注释

原理图绘制

  1. 芯片数据手册阅读
    1. CH340G:重点关注 USB 差分信号(D+/D-)处理
      1. 参考资料链接:CH340G 数据手册
    2. AMS1117:输入/输出电压稳定设计,需添加去耦电容
      2. 参考资料链接:AMS1117 数据手册
  2. 模块绘制:
    1. 供电模块:5V 转 3.3V 电路
    2. 通信模块:BM-H63 与 CH340 的 UART 连接
  3. 标注规范:
    1. 网络标签命名规则(如 VCC_3V3、UART1_TX)
    2. 添加设计注释

Layout

  1. 布局策略:
    1. 模块分区
    2. 布线设计:避免直角转弯
  2. 铺铜处理:
    1. 顶层/底层整板覆铜(GND 网络),添加缝合孔
  3. 设计验证:
    2. DRC 检查
    3. 输出 Gerber 文件
  4. PCB 下单

元件焊接

  1. 焊接安全注意事项
  2. 焊接操作注意事项
  3. 焊接质量检测
    1. 焊点光泽度
    2. 引脚无桥接、虚焊
    3. 万用表导通测试


软件设计

环境配置: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
  1. 创建任务句柄
  2. 锁定调度
  3. 创建任务
  4. 设置任务优先级
  5. 释放任务句柄
  6. 解锁调度

示例代码:

// 来自 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 链接
  • 数据处理

任务:

  1. 测试星闪通信是否正常
  2. 通过 UART 向 Server 端输入 姓名,学号,年龄 数据,处理后发送给 Client
    1. 在数据前加上前缀,使其格式变为 name:姓名,num:学号,age:年龄
    2. Client 根据接收到的数据的最后一位的奇偶点亮板载 LED(奇数点亮,偶数熄灭)
Workflow
Server
Client
Server
Client
接收
发送
程序入口
设备类型判断
Server初始化
Client初始化
配置UART
注册回调
设备角色
循环监听连接状态
自动连接Server
数据收发
通过UART输出
通过SLE传输

项目文件结构

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, &param.uuid);                  // 设置 UUIDret = ssaps_notify_indicate_by_uuid(g_server_id, g_sle_conn_hdl, &param); // 发送通知/指示,具体发送状态取决于客户端特征配置描述符值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, &param);
}/*** @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, &parameter);
}// 注册连接回调函数
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, &param);
}// 设置默认广播数据
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(&param);                         // 设置扫描参数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);

相关文章:

LiteOS与SLE透传实战案例

文章目录 硬件设计EDA 软件介绍创建元件及封装原理图绘制Layout元件焊接 软件设计LiteOS 入门核心概念TaskWorkflow参考 API&#xff08;参考 osal_task. h&#xff09; 时间片任务轮转练习&#xff1a;仿写 example/peripheral/blinkyQueue参考 API&#xff08;参考 osal_msgq…...

MCAL学习(1)——AutoSAR

1.了解AutoSAR及一些概念 AutoSAR是Automotive Open System Architecture ,汽车开放系统架构。 针对汽车ECU的软件开发架构。已经是汽车电子软件开发的标准。 OS服务&#xff1a;Freertos 整车厂&#xff08;OEM&#xff09;主要负责应用层算法 一级供应商&#xff1a;生产制…...

前端vue3项目学习

鸽王经过一个多月的学习&#xff08;断断续续吧&#xff0c;毕竟还有其他杂事&#xff09;&#xff0c;学的昏天黑地&#xff0c;终于把主线捋的差不多了。只能说&#xff0c;前端真不是人学的&#xff0c;涉及的语言语法太杂乱了&#xff0c;入门真的太难了。而后端&#xff0…...

ActiveMQ 性能优化与网络配置实战(二)

五、性能优化实战 5.1 基础配置调整 5.1.1 增加并发消费者 在 ActiveMQ 中&#xff0c;增加并发消费者是提高消息处理效率的重要手段之一。通过配置多个消费者并行处理消息&#xff0c;可以充分利用系统资源&#xff0c;加快消息的消费速度&#xff0c;从而提高系统的整体吞…...

Python 函数装饰器和闭包(装饰器基础知识)

本章内容&#xff1a; Python 如何计算装饰器句法 Python 如何判断变量是不是局部的 闭包存在的原因和工作原理 nonlocal 能解决什么问题 掌握这些基础知识后&#xff0c;我们可以进一步探讨装饰器&#xff1a; 实现行为良好的装饰器 标准库中有用的装饰器 实现一个参数化装饰器…...

“Everything“工具 是 Windows 上文件名搜索引擎神奇

01 Everything 和其他搜索引擎有何不同 轻量安装文件。 干净简洁的用户界面。 快速文件索引。 快速搜索。 快速启动。 最小资源使用。 轻量数据库。 实时更新。 官网&#xff1a;https://www.voidtools.com/zh-cn/downloads/ 通过网盘分享的文件&#xff1a;Every…...

【Machine Learning Q and AI 读书笔记】- 04 彩票假设

Machine Learning Q and AI 中文译名 大模型技术30讲&#xff0c;主要总结了大模型相关的技术要点&#xff0c;结合学术和工程化&#xff0c;对LLM从业者来说&#xff0c;是一份非常好的学习实践技术地图. 本文是Machine Learning Q and AI 读书笔记的第4篇&#xff0c;对应原…...

linux下安装ollama网不好怎么办?

文章目录 前言kkgithub下载脚本,而不是直接运行修改脚本修改权限还是不行?前言 今天想在linux上面更新一下ollama,于是去到官网: https://ollama.com/download/linux linux下安装ollama还是挺简单的: curl -fsSL https://ollama.com/install.sh | sh我也是特别嗨皮地就…...

(A题|支路车流量推测问题)2025年第二十二届五一数学建模竞赛(五一杯/五一赛)解题思路|完整代码论文集合

我是Tina表姐&#xff0c;毕业于中国人民大学&#xff0c;对数学建模的热爱让我在这一领域深耕多年。我的建模思路已经帮助了百余位学习者和参赛者在数学建模的道路上取得了显著的进步和成就。现在&#xff0c;我将这份宝贵的经验和知识凝练成一份全面的解题思路与代码论文集合…...

RDMA高性能网络通信实践

RDMA高性能网络通信实践 一、背景介绍二、方法设计A.实现方案B.关键技术点三、代码及注释四、注意事项一、背景介绍 远程直接内存访问(RDMA)技术通过绕过操作系统内核和CPU直接访问远程内存,实现了超低延迟、高吞吐量的网络通信。该技术广泛应用于高性能计算、分布式存储和…...

【LeetCode Hot100】图论篇

前言 本文用于整理LeetCode Hot100中题目解答&#xff0c;因题目比较简单且更多是为了面试快速写出正确思路&#xff0c;只做简单题意解读和一句话题解方便记忆。但代码会全部给出&#xff0c;方便大家整理代码思路。 200. 岛屿数量 一句话题意 求所有上下左右的‘1’的连通块…...

图论---有向图的强连通分量(Tarjan求SCC)

强连通分量作用&#xff1a;有向图——>&#xff08;缩点&#xff09;有向无环图&#xff08;DAG&#xff09; 缩点&#xff1a;将所有连通的分量缩成一个点。 有向无环图作用/好处&#xff1a;求最短路/最长路 可以递推&#xff0c;按照拓扑图从前往后递推. x 是否在某个…...

2025年- H17-Lc125-73.矩阵置零(矩阵)---java版

1.题目描述 2.思路 &#xff08;1&#xff09;计算矩阵的行数 &#xff08;2&#xff09;计算矩阵的列数 &#xff08;3&#xff09;设计一个行列的bool数组 &#xff08;4&#xff09;遍历矩阵&#xff08;二维数组&#xff09;&#xff0c;如果遇到元素0&#xff0c;则把…...

【信息系统项目管理师-论文真题】2023下半年论文详解(包括解题思路和写作要点)

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 试题(第一批):论信息系统项目的干系人管理1、写作要点2、解题思路项目干系人管理的过程和执行要点项目中所有干系人如何进行分类管理?试题(第二批):论信息系统项目的工作绩效域1、写作要点2、解题思路绩…...

分享5款开源、美观的 WinForm UI 控件库

前言 今天大姚给大家分享5款开源、美观的 WinForm UI 控件库&#xff0c;助力让我们的 WinForm 应用更好看。 WinForm WinForm是一个传统的桌面应用程序框架&#xff0c;它基于 Windows 操作系统的原生控件和窗体。通过简单易用的 API&#xff0c;开发者可以快速构建基于窗体…...

微软推出数款Phi 4“开放式”人工智能模型

微软周三推出了几款新的“开放式”人工智能模型&#xff0c;其中功能最强大的模型至少在一个基准测试上可与 OpenAI 的 o3-mini 相媲美。所有新的授权模型——Phi 4 mini reasoning、Phi 4 reasoning 和 Phi 4 reasoning plus——都是“推理”模型&#xff0c;这意味着它们能够…...

Python实例题:Python实现Python解释器

目录 Python实例题 题目 实现思路 代码实现 代码解释 词法分析器&#xff08;Lexer&#xff09;&#xff1a; 词法单元类&#xff08;Token&#xff09;&#xff1a; 抽象语法树节点类&#xff08;AST&#xff09;&#xff1a; 语法分析器&#xff08;Parser&#xff…...

【IP101】图像滤波技术详解:从均值滤波到高斯滤波的完整指南

&#x1f31f; 图像滤波魔法指南 &#x1f3a8; 在图像处理的世界里&#xff0c;滤波就像是给图片"美颜"的魔法工具。让我们一起来探索这些神奇的滤波术吧&#xff01; &#x1f4d1; 目录 1. 均值滤波&#xff1a;图像的"磨皮"大法2. 中值滤波&#xff1…...

信息系统项目管理师-软考高级(软考高项)​​​​​​​​​​​2025最新(六)

个人笔记整理---仅供参考 第六章项目管理概论 6.1PMBOK的发展 6.2项目基本要素 组织过程资产指的是项目上的&#xff0c;国产数据库的使用----安保和安全指的是环境因素 6.3项目经理的角色 6.4价值驱动的项目管理知识体系...

算法-堆、排序算法、矩阵乘法

满二叉树、完全二叉树 二叉树遵循下面的规律&#xff0c;当前节点i&#xff08;但是其实就是逐级填充&#xff09;: 左节点为 ix2右节点为 i*21父节点为 [i/2] 向下取整 使用数组表示二叉树&#xff1a; &#xff08;二叉树的深度自上而下&#xff0c;高度自下…...

Java 进阶--集合:告别数组的“僵硬”,拥抱灵活的数据容器

作者&#xff1a;IvanCodes 发布时间&#xff1a;2025年5月1日&#x1fae1; 专栏&#xff1a;Java教程 大家好&#xff01;&#x1f44b; 还记得我们上次聊的数组 (Array) 吗&#xff1f;它很基础&#xff0c;性能也不错&#xff0c;但有个致命的缺点&#xff1a;长度一旦定…...

Python 数据智能实战 (6):用户评论深度挖掘

写在前面 —— 从繁杂评论到精准洞察:主题发现与情感趋势分析,驱动产品优化与体验提升 在上篇内容中,我们学习了如何利用 LLM 提升用户分群的精度,以及如何超越传统购物篮分析,挖掘商品间的语义关联。今天,我们将聚焦于电商领域 价值密度最高 的非结构化数据之一——用…...

podman/docker国内可用的docker镜像源(2025-05)

一、添加Docker国内镜像 1、修改 /etc/docker/daemon.json 设置 registry mirror&#xff0c;具体命令如下: sudo vim /etc/docker/daemon.json <<EOF {"registry-mirrors": ["https://docker.1ms.run","https://docker.xuanyuan.me",&q…...

机器人--底盘

机器人底盘 底盘是机器人传感器和机器人主机的载体&#xff0c;也是移动机器人的基本形式。移动机器人通常可以采用轮式和足式进行移动。 也就是机器人底盘可以分为轮式底盘和足式底盘。 足式底盘控制复杂&#xff0c;这里只讨论轮式底盘。 底盘运动学模型 轮式机器人底盘按…...

Seata服务端同步提交事务核心源码解析

文章目录 前言一、doGlobalCommit&#xff08;同步提交&#xff09;2.1、closeAndClean()2.2、changeGlobalStatus2.3、doGlobalCommit2.3.1、findGlobalSession 总结 前言 本篇介绍Seata服务端TC如何驱动RM提交事务。 一、doGlobalCommit&#xff08;同步提交&#xff09; doG…...

2025五一杯B题五一杯数学建模思路代码文章教学: 矿山数据处理问题

完整内容请看文章最下面的推广群 问题1. 根据附件1中的数据和&#xff0c;建立数学模型&#xff0c;对数据A进行某种变换&#xff0c;使得变换后的结果与数据尽可能接近。计算变换后的结果与数据的误差&#xff0c;并分析误差的来源&#xff08;如数据噪声、模型偏差等&#xf…...

C++11新特性_自动类型推导

decltype 和 auto 均为 C 里用于类型推导的关键字&#xff0c;不过它们在使用方式、推导规则和应用场景上存在显著差异。下面为你详细介绍它们的区别&#xff1a; 1. 推导依据 auto&#xff1a;它依据变量的初始化表达式来推导类型。也就是说&#xff0c;auto 定义的变量必须有…...

【AI论文】ReasonIR:为推理任务训练检索器

摘要&#xff1a;我们提出了ReasonIR-8B&#xff0c;这是第一个专门针对一般推理任务进行训练的检索器。 现有的检索器在推理任务上表现出的收益有限&#xff0c;部分原因是现有的训练数据集侧重于与直接回答它们的文档相关的简短事实查询。 我们开发了一个合成数据生成管道&am…...

嵌入式AI还是一片蓝海

发现其实还是挺多人关注嵌入式和人工智能交叉领域的&#xff0c;随便一个问题&#xff0c;浏览量就27万了&#xff0c;但是这方面的内容确实少得可怜……所以干脆我自己来补点干货。 推荐一本最近很热门的新书——《边缘人工智能&#xff1a;用嵌入式机器学习解决现实问题》。 …...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(13): ておきます ています & てあります

日语学习-日语知识点小记-构建基础-JLPT-N4阶段&#xff08;13&#xff09;&#xff1a; ておきます &ています &#xff06; てあります 。 1、前言&#xff08;1&#xff09;情况说明&#xff08;2&#xff09;工程师的信仰 2、知识点&#xff08;1&#xff09;&#x…...

CMake管理外部依赖的模块

在 CMake 中&#xff0c;FetchContent 和 ExternalProject 都是管理外部依赖的模块&#xff0c;但它们的 设计目标、使用场景和执行时机 有本质区别。以下通过对比表格、代码示例和场景分析详细说明它们的区别。 核心区别对比表 特性FetchContentExternalProject执行阶段配置阶…...

[计算机科学#7]:CPU的三阶段,取指令、解码、执行

【核知坊】&#xff1a;释放青春想象&#xff0c;码动全新视野。 我们希望使用精简的信息传达知识的骨架&#xff0c;启发创造者开启创造之路&#xff01;&#xff01;&#xff01; 内容摘要&#xff1a;本文详细介绍了CPU的工作原理&#xff0c;包括其结构…...

向量数据库和关系型数据库的区别,优点,缺点和典型应用场景

向量数据库与关系型数据库的全面对比 向量数据库和关系型数据库是两种截然不同的数据管理系统&#xff0c;各自针对特定的数据模型和查询模式进行了优化。随着人工智能和大数据技术的发展&#xff0c;向量数据库作为新兴的数据库类型&#xff0c;在处理非结构化数据方面展现出…...

《跨越边界:探索跨端框架中通用状态管理方案设计》

一款应用往往需要在多个终端&#xff0c;如Web、移动端、桌面端等同时运行&#xff0c;以满足用户多元化的使用场景。在这复杂的跨端开发领域中&#xff0c;状态管理堪称关键枢纽&#xff0c;直接关乎应用的性能、稳定性以及开发与维护的效率。如何设计一套通用的状态管理方案&…...

PHP之CURL通过header传参数及接收

一、传参数之冒号 注意一点&#xff0c;这里的header数据不是KV结构&#xff0c;而是一个一维数组。 看清楚&#xff0c;注意一点&#xff0c;是这样的结构&#xff1a; $ch curl_init(); $headers [X-Custom-Header: value123,Authorization: Bearer your_token_here // …...

【C++】brpc安装

brpc安装教程 环境&#xff1a;Ubuntu24.04 1 简单安装 即安装到系统环境下&#xff0c;依赖也是依赖apt安装。 官方参考教程 依赖准备 安装依赖&#xff1a; sudo apt-get install -y git g make libssl-dev libgflags-dev libprotobuf-dev libprotoc-dev protobuf-com…...

从0开始的c++知识讲解之字符串(1)

作者作为新手&#xff0c;对于知识的讲解也是边输出内容也是边学习&#xff0c;如有缺陷&#xff0c;请多海涵&#xff0c;但同样&#xff0c;我会帮助你从新手视角看到新手的疑惑&#xff0c;并帮助你解决此疑惑 一&#xff0c;开宗明义&#xff0c;立意先行 string在C里有可…...

Linux 第六讲 --- 工具篇(一)yum/apt与vim

前言&#xff1a; 经过前五讲对Linux基础指令与权限系统的系统学习&#xff0c;相信你已经能在命令行中自如地穿梭于文件丛林&#xff0c;精准调配权限密钥。但真正的Linux玩家&#xff0c;绝不会止步于基础操作的重复劳作。 从今天起&#xff0c;我们将打开Linux的"瑞士…...

xml 和 yaml 的区别

XML 和 YAML/YML 是两种常用的数据序列化格式&#xff0c;用于存储和读取结构化数据。以下是它们的核心区别和使用方法&#xff1a; 1. 格式特性对比 特性XMLYAML/YML语法复杂度标签嵌套&#xff0c;结构严格缩进分层&#xff0c;更简洁可读性较低&#xff08;冗余标签&#…...

1.67g 雨晨 22635.5305 Windows 11 企业版 23H2 极速增强版

五一特别制作 &#xff08;主要更新简述&#xff09; 全程由最新YCDISM2025装载制作 1、可选功能&#xff1a; 添加&#xff1a; Microsoft-Windows-LanguageFeatures-Basic-en-us-Package Microsoft-Windows-LanguageFeatures-OCR-en-us-Package 2、功能增强&a…...

【C++】类和对象(中)——默认成员函数详解(万字)

文章目录 上文链接类的默认成员函数1. 构造函数(1) 什么是构造函数(2) 构造函数的使用 2. 析构函数(1) 什么是析构函数(2) 析构函数的使用(3) 小练习 3. 拷贝构造函数(1) 什么是拷贝构造函数(2) 拷贝构造函数的使用 4. 赋值运算符重载(1) 运算符重载(2) 运算符重载的简单应用(3…...

Ubuntu18 登录界面死循环 Ubuntu进不了桌面

今天碰到这个问题&#xff0c;真是把我恶心到了 网上很多方法都不靠谱&#xff0c;最后我还是自己摸索出一个方法 先进入终端 开机后在登陆界面按下shift ctrl F1&#xff08;或者F2&#xff0c;一直按&#xff09;进入tty命令行终端登陆后输入(本人的用户名为hp&#xff…...

caffe适配cudnn9.6.0(ai修改代码踩坑)

caffe适配cudnn&#xff1a;https://github.com/dyc2424748461/caffe &#xff08;测试一下&#xff0c;成没成&#xff0c;反正我看到它用gpu了&#x1f636;&#xff09; 因为突发奇想&#xff0c;想要玩easymocap&#xff0c;先是简单使用media跑通了一下&#xff0c;然后过…...

【MySQL数据库】视图

1&#xff0c;视图的基本介绍 视图是一个虚拟表&#xff0c;其内容由查询定义。与真实表一样的是&#xff0c;视图包含带有名称的列和行数据&#xff1b;与真实表不一样的是&#xff0c;视图本身并不在数据库中存储数据。视图的数据变化会影响到基表&#xff0c;基表的数据变化…...

Linux日常使用与运维的AI工具全景调研:效率革命的终极指南

Linux日常使用与运维的AI工具全景调研:效率革命的终极指南 引言:当Linux遇上AI,运维世界正在发生什么? 作为一名Linux系统管理员,你是否还在为以下问题困扰: 深夜被报警短信惊醒,却要手动排查复杂的系统故障?面对海量日志文件,像大海捞针一样寻找关键错误信息?重复…...

Linux——线程(3)线程同步

一、线程同步的引入 通过上面的抢票系统我们发现&#xff0c;有的线程&#xff0c;进行工作&#xff08;挂锁&#xff09;&#xff0c;当其马上结束工作&#xff08;解锁&#xff09;&#xff0c;发现外面有很多线程在排队等着加锁执行任务&#xff0c;这个线程解锁后就立马给…...

Redis实现分布式锁

分布式锁是分布式系统中解决资源竞争问题的重要机制。Redis凭借其高性能和原子性操作&#xff0c;成为实现分布式锁的热门选择。本文将详细介绍如何使用Java和Redis实现分布式锁&#xff0c;并重点讲解如何通过Lua脚本保证锁操作的原子性。 一、分布式锁的基本要求 一个可靠的…...

JavaScript如何实现类型判断?

判断一个数据的类型&#xff0c;常用的方法有以下几种&#xff1a; typeofinstanceofObject.prototype.toString.call(xxx) 下面来分别分析一下这三种方法各自的优缺点 typeof typeof的本意是用来判断一个数据的数据类型&#xff0c;所以返回的也是一个数据类型。但是会遇到下…...

Spring MVC 与 FreeMarker 整合

以下是 Spring MVC 与 FreeMarker 整合的详细步骤&#xff0c;包含配置和代码示例&#xff1a; 1. 添加依赖 在 pom.xml 中引入 Spring MVC 和 FreeMarker 的依赖&#xff08;以 Maven 为例&#xff09;&#xff1a; <!-- Spring Web MVC --> <dependency><gr…...

设计模式简述(十五)观察者模式

观察者模式 描述基本组件使用 描述 观察者模式&#xff0c;顾名思义就是一个对象观察着其他对象&#xff0c;一旦被观察的对象发生变化时&#xff0c;观察者对象也要做出相应动作。 其中&#xff0c;被观察者持有观察者的引用。由观察者主动注入被观察者内&#xff08;有点像…...