# 73-HiSilicon Smart-Stick **Repository Path**: yangdev89/73-hi-silicon-smart-stick ## Basic Information - **Project Name**: 73-HiSilicon Smart-Stick - **Description**: 星闪体验官73号-星幻魔控·海思智棒项目 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2024-10-31 - **Last Updated**: 2025-09-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 星闪体验官案例73-星幻魔控·海思智棒 [![MIT License][license-shield]][license-url] **唤醒那个相信光的男人!**TIGA! [TOC] # 1 作品介绍 **本作品代码仓库:** https://gitee.com/a-yang-studies-hard/73-hi-silicon-smart-stick **SDK仓库:** https://gitee.com/HiSpark/fbb_ws63 **功能介绍:** **你是否曾经梦想成为光之巨人,与迪迦奥特曼一起守护地球的和平?现在,有了这款神奇的智能家居遥控器,你的梦想即将成真!** **星幻魔控·海思智棒**是一款集成了**星闪SLE 1.0**技术的智能家居遥控器,其独特的设计灵感来源于迪迦奥特曼的变身棒,旨在为用户带来一种前所未有的智能控制体验。通过变身棒式的外形设计与直观的交互方式,用户可以轻松控制家中的各种智能家居设备,享受科技带来的便捷生活。 虽然我们不能真的成为奥特曼,但有了这款 **“海思智棒”** ,家居控制将变得简单有趣,仿佛我们也能拥有一种神奇的力量!

**作品完成情况:**

- [X] 客户端-海思智棒(基于星闪1对x) - [X] 服务端-智能插座控制(基于继电器) - [x] 服务端-智能灯光控制(基于SG90舵机) - [X] 服务端-智能空调控制(基于红外学习模块) - [X] MPU6050动作识别(已实现8种不同动作) **核心功能介绍:** **1.星闪客户端控制:** 海思智棒作为客户端,采用**星闪1对多**技术实现与3种智能家居设备的无线连接。用户可以通过模仿迪迦奥特曼变身棒的动作,轻松操作家中的智能设备。

**2.MPU6050动作识别:** 内置MPU6050传感器实现动作识别功能,目前已经支持**8种不同的手势动作**,用户可以通过特定的手势来触发相应的控制指令。

**3.智能插座控制:** 基于继电器的智能插座控制系统,**允许用户通过海思智棒远程开启或关闭家中的电器设备**,实现智能化管理。

**4.智能灯光控制:** 利用SG90舵机实现智能灯光的控制功能,**用户可以通过挥动海思智棒来调整室内灯光开关**,减少日常开关灯的带来的麻烦。

**5.智能空调控制:** 集成红外学习模块的智能空调控制系统,**用户可以通过海思智棒发送预设的红外信号来调节空调的开关、温度等设置。**

**注:动作支持录制,智能设备可扩展,上方仅本次预设动作** ## 1.1 文件目录说明 ``` 73-HiSilicon Smart-Stick ├── LICENSE.txt ├── README.md ├── sle_ac_server // 服务端-智能空调控制 │ ├── CMakeLists.txt │ ├── inc │ │ ├── infrared.h │ │ ├── SLE_LED_Server.h │ │ └── SLE_LED_Server_adv.h │ └── src │ ├── infrared.c │ ├── SLE_LED_Server.c │ └── SLE_LED_Server_adv.c ├── sle_cz_server // 服务端-智能插座控制 │ ├── CMakeLists.txt │ ├── inc │ │ ├── IM1281B_demo.h │ │ ├── relay.h │ │ ├── SLE_SOCKET_Server.h │ │ └── SLE_SOCKET_Server_adv.h │ └── src │ ├── IM1281B_demo.c │ ├── relay.c │ ├── SLE_SOCKET_Server.c │ └── SLE_SOCKET_Server_adv.c ├── sle_servo_server // 服务端-智能灯光控制 │ ├── CMakeLists.txt │ ├── inc │ │ ├── Servo.h │ │ ├── SLE_LED_Server.h │ │ └── SLE_LED_Server_adv.h │ └── src │ ├── Servo.c │ ├── SLE_LED_Server.c │ └── SLE_LED_Server_adv.c └── sle_smart_client // 客户端-海思智棒 ├── CMakeLists.txt ├── inc │ ├── mpu6050.h │ └── sle_client.h └── src ├── mpu6050.c └── sle_client.c ``` ## 1.2 约束与限制 ### 1.2.1 支持应用运行的芯片和开发板 本作品支持开发板:HiHope_NearLink_DK3863E_V03 ### 1.2.2 支持API版本、SDK版本 本作品支持版本号:1.10.101 ### 1.2.3 支持IDE版本 本作品支持IDE版本号:1.0.0.6; ## 1.3 作品实现效果

**整体展示效果视频链接:** 73-HiSilicon Smart-Stick-演示视频 链接:https://pan.baidu.com/s/15etGvrEBp8vhBYI1PYRBCw?pwd=1234 提取码:1234 # 2 作品开发指南 ## 2.1 Client端-海思智棒 ### 2.1.1 海思智棒-硬件实现 **海思智棒硬件组成:** 1.HiHope_NearLink_DK3863E_V03核心板x1

2.MPU6050传感器x1

3.智棒整体接线演示

注:整个Client端程序里我也有用到两个按钮,主要作用是通过按钮触发GPIO中断回调,以此实现控制手势动作数据采集功能开启的控制效果,这里不再赘述,开发者朋友们可以根据实际情况来实现按钮触发功能。 ### 2.1.2 海思智棒-软件实现 1.Client端程序的整体设计思想参考**星闪1连多**,即Client端扫描到不同Server端的广播,并依次建立连接,通过提前预设好不同Server端的address,来实现对不同Server端发送控制信号。 **下方是核心部分代码** ````c // 该部分程序展示如何对对不同Server端发送控制信号 if (flag == 1) { // 分配内存空间,用于存放发送的数据 shuju.data = malloc(8); // 夂制字符串 "LED_ON" 到分配的内存空间中 memcpy(shuju.data, "LED_ON", 7); // 设置 handle 值 shuju.handle = 0; // 设置 type 值 shuju.type = 0; // 设置 data 的长度 shuju.data_len = 7; // 创建一个临时地址对象 sle_addr_t temp_addr; // 复制目标服务器地址到临时地址对象中 memcpy(temp_addr.addr, addr_servo, SLE_ADDR_LEN); // 检查当前连接的设备是否为目标设备 if (compare_addr(&temp_addr, &g_connected_devices[g_connected_device_count].addr)) { PRINT("当前连接设备即目标设备\r\n"); // 打印当前连接设备的地址信息 print_addr(&g_connected_devices[g_connected_device_count].addr, "Connected device"); // 打印临时地址对象的信息 print_addr(&temp_addr, "Temp device"); // 发送通知给客户端 example_sle_Client_send_notify_by_handle(shuju.data, sizeof(ssapc_write_param_t)); // 释放内存 free(shuju.data); } else { // 查找目标设备的索引值 uint8_t device_index = find_device_index_by_addr(&temp_addr); // 更新连接 ID 为找到的目标设备的连接 ID g_conn_id = g_connected_devices[device_index].conn_id; // 打印当前连接设备的地址信息 print_addr(&g_connected_devices[g_connected_device_count].addr, "Connected device"); // 打印临时地址对象的信息 print_addr(&temp_addr, "Temp device"); // 输出提示信息,表示当前连接设备不是目标设备,并更新了连接 ID PRINT("当前连接设备非目标设备,g_conn_id已切换:%d 找到的索引值为%d\r\n", g_conn_id, device_index); // 发送通知给客户端 example_sle_Client_send_notify_by_handle(shuju.data, sizeof(ssapc_write_param_t)); // 释放内存 free(shuju.data); } } else if (flag == 2) { // 分配内存空间,用于存放发送的数据 shuju.data = malloc(9); // 复制字符串 "LED_OFF" 到分配的内存空间中 memcpy(shuju.data, "LED_OFF", 8); // 设置 handle 值 shuju.handle = 0; // 设置 type 值 shuju.type = 0; // 设置 data 的长度 shuju.data_len = 8; // 创建一个临时地址对象 sle_addr_t temp_addr; // 复制目标服务器地址到临时地址对象中 memcpy(temp_addr.addr, addr_servo, SLE_ADDR_LEN); // 检查当前连接的设备是否为目标设备 if (compare_addr(&temp_addr, &g_connected_devices[g_connected_device_count].addr)) { // 发送通知给客户端 example_sle_Client_send_notify_by_handle(shuju.data, sizeof(ssapc_write_param_t)); // 释放内存 free(shuju.data); } else { // 查找目标设备的索引值 uint8_t device_index = find_device_index_by_addr(&temp_addr); // 更新连接 ID 为找到的目标设备的连接 ID g_conn_id = g_connected_devices[device_index].conn_id; // 输出提示信息,表示当前连接设备不是目标设备,并更新了连接 ID PRINT("当前连接设备非目标设备,g_conn_id已更改:%d 索引值为%d\r\n", g_conn_id, device_index); // 发送通知给客户端 example_sle_Client_send_notify_by_handle(shuju.data, sizeof(ssapc_write_param_t)); // 释放内存 free(shuju.data); } } ```` 2.MPU6050驱动,参考sle_smart_client\src\mpu6050.c文件,主要通过IIC通信实现驱动,并加入卡尔曼滤波和姿态角解算,方便后续进行动作识别。 **核心部分代码** ````c void MPU6050_GetData(SensorData *data) { uint8_t DataH, DataL; // 定义数据高8位和低8位的变量 // 初始化Kalman滤波器 kalman_filter_t kf_acc_x, kf_acc_y, kf_acc_z; kalman_filter_t kf_gyro_x, kf_gyro_y, kf_gyro_z; // 例如,Q = 0.001, R = 0.03, X = 初始估计值, P = 初始协方差估计值 kalman_filter_init(&kf_acc_x, 0.001, 0.03, 0.0, 1.0); kalman_filter_init(&kf_acc_y, 0.001, 0.03, 0.0, 1.0); kalman_filter_init(&kf_acc_z, 0.001, 0.03, 0.0, 1.0); kalman_filter_init(&kf_gyro_x, 0.001, 0.03, 0.0, 1.0); kalman_filter_init(&kf_gyro_y, 0.001, 0.03, 0.0, 1.0); kalman_filter_init(&kf_gyro_z, 0.001, 0.03, 0.0, 1.0); // 读取加速度计和陀螺仪数据 DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); // 读取加速度计X轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); // 读取加速度计X轴的低8位数据 AccX = (DataH << 8) | DataL; // 数据拼接 data->acc_x = kalman_filter_update(&kf_acc_x, AccX / 16384.0f); // 应用Kalman滤波器并转换单位 DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); // 读取加速度计Y轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); // 读取加速度计Y轴的低8位数据 AccY = (DataH << 8) | DataL; // 数据拼接 data->acc_y = kalman_filter_update(&kf_acc_y, AccY / 16384.0f); // 应用Kalman滤波器并转换单位 DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); // 读取加速度计Z轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); // 读取加速度计Z轴的低8位数据 AccZ = (DataH << 8) | DataL; // 数据拼接 data->acc_z = kalman_filter_update(&kf_acc_z, AccZ / 16384.0f); // 应用Kalman滤波器并转换单位 DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); // 读取陀螺仪X轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); // 读取陀螺仪X轴的低8位数据 GyroX = (DataH << 8) | DataL; // 数据拼接 data->gyro_x = kalman_filter_update(&kf_gyro_x, GyroX / 131.0f); // 应用Kalman滤波器并转换单位 DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); // 读取陀螺仪Y轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); // 读取陀螺仪Y轴的低8位数据 GyroY = (DataH << 8) | DataL; // 数据拼接 data->gyro_y = kalman_filter_update(&kf_gyro_y, GyroY / 131.0f); // 应用Kalman滤波器并转换单位 DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); // 读取陀螺仪Z轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); // 读取陀螺仪Z轴的低8位数据 GyroZ = (DataH << 8) | DataL; // 数据拼接 data->gyro_z = kalman_filter_update(&kf_gyro_z, GyroZ / 131.0f); // 应用Kalman滤波器并转换单位 char kalman_strings[6][20]; snprintf(kalman_strings[0], sizeof(kalman_strings[0]), "%.2f", data->acc_x); snprintf(kalman_strings[1], sizeof(kalman_strings[1]), "%.2f", data->acc_y); snprintf(kalman_strings[2], sizeof(kalman_strings[2]), "%.2f", data->acc_z); snprintf(kalman_strings[3], sizeof(kalman_strings[3]), "%.2f", data->gyro_y); snprintf(kalman_strings[4], sizeof(kalman_strings[4]), "%.2f", data->gyro_x); snprintf(kalman_strings[5], sizeof(kalman_strings[5]), "%.2f", data->gyro_z); // 输出格式化后的字符串 printf("MPU6050_Data=kalman_acc_x:%s,kalman_acc_y:%s,kalman_acc_z:%s,kalman_gyro_x:%s,kalman_gyro_y:%s,kalman_gyro_z:%s\r\n", kalman_strings[0], kalman_strings[1], kalman_strings[2], kalman_strings[3], kalman_strings[4], kalman_strings[5]); } ```` ### 2.1.3 海思智棒-识别8种不同动作 **基础动作识别** 基础动作识别有四种,通过换算姿态角的方式实现,即 **“上挥”、“下挥”、“左旋”、“右旋”** **姿态角解算代码如下** ````c void calculate_accel_angle(int16_t AccX, int16_t AccY, int16_t AccZ) { // 计算角度 current_angle.roll = atan2(AccX, sqrt(AccY * AccY + AccZ * AccZ)) * 180.0f / M_PI; current_angle.pitch = atan2(-AccY, AccZ) * 180.0f / M_PI; } // 从陀螺仪数据更新角度 void update_gyro_angle(float GyroX, float GyroY, float GyroZ, float dt) { // 积分得到角度 current_angle.pitch += GyroX * dt*100; current_angle.roll += GyroY * dt*100; current_angle.yaw += GyroZ * dt*100; } // 上方两个函数具体调用用法如下 readSensorData(motion1Samples); // 转换加速度计数据为角度 calculate_accel_angle(motion1Samples->acc_x, motion1Samples->acc_y, motion1Samples->acc_z); // 时间间隔 float dt = I2C_TASK_DURATION_MS / 1000.0f; // 使用陀螺仪数据更新角度 update_gyro_angle(motion1Samples->gyro_x, motion1Samples->gyro_y, motion1Samples->gyro_z, dt); ```` **高级动作识别** 基础动作识别一样预设四种,通过欧几里得距离对比的方式实现,即 **“迪迦变身”、“L”、“I”、“C”** 在做高级动作识别的时候需要先测算出预期动作的MPU6050传感器6轴具体数值,可手动做相同动作重复30组(根据实际情况),再求出平均值即得出该动作的预设值,方便欧几里得距离测算。 **高级动作识别代码如下** ````c // 计算两个 n 维空间点之间的欧几里得距离 float calculateEuclideanDistance(SensorData current, SensorData reference) { // 初始化平方差的总和 float sum = pow((current.acc_x - reference.acc_x), 2) + pow((current.acc_y - reference.acc_y), 2) + pow((current.acc_z - reference.acc_z), 2) + pow((current.gyro_x - reference.gyro_x), 2) + pow((current.gyro_y - reference.gyro_y), 2) + pow((current.gyro_z - reference.gyro_z), 2); // 计算总和的平方根,得到欧几里得距离 return sqrt(sum); } // 检查当前传感器读数是否匹配已知的手势 int isGestureMatched(SensorData current, SensorData reference, float threshold) { // 当前函数未使用阈值参数 unused(threshold); // 计算当前传感器数据与参考传感器数据之间的欧几里得距离 float distance = calculateEuclideanDistance(current, reference); // 将距离转换为整数(根据应用场景,这一步可能不需要) int dis = (int)distance; // 返回距离的整数值 return dis; // 以检查距离是否落在特定范围内,然后再返回匹配结果。 } // 对多个传感器读数取平均值,得到手势的平均表示 void averageGestureSamples(SensorData samples[], int numSamples, SensorData *average) { // 使用零初始化平均传感器数据结构 memset(average, 0, sizeof(SensorData)); // 汇总所有传感器读数 for (int i = 0; i < numSamples; i++) { *average = (SensorData){ .acc_x = average->acc_x + samples[i].acc_x, .acc_y = average->acc_y + samples[i].acc_y, .acc_z = average->acc_z + samples[i].acc_z, .gyro_x = average->gyro_x + samples[i].gyro_x, .gyro_y = average->gyro_y + samples[i].gyro_y, .gyro_z = average->gyro_z + samples[i].gyro_z}; } // 将每个分量除以样本数量,得到平均值 average->acc_x /= numSamples; average->acc_y /= numSamples; average->acc_z /= numSamples; average->gyro_x /= numSamples; average->gyro_y /= numSamples; average->gyro_z /= numSamples; } ```` 如果开发者朋友们需要新增手势动作的话,可以按照这个办法,先求出目标动作的初始值,再通过这种欧几里得距离对比法,实现特定动作的识别,**实测准确率有70%左右哦!** ## 2.2 Server端1-智能空调控制 ### 2.2.1 智能空调控制-硬件实现 **海思智棒硬件组成:** 1.HiHope_NearLink_DK3863E_V03核心板x1

2.红外学习模块x1

3.整体接线图(其实是UART原理)

### 2.2.2 智能空调控制-软件实现 该Server端的核心是**UART通信**,接收到来自Client端的星闪信号后,WS63E会通过UART向红外学习模块发送数据,进而该红外学习模块会对我们需要遥控的设备(空调等)发出红外信号进行控制,我这边预设了几种已经学习好的控制案例,开发者朋友们可以在sle_ac_server\src\infrared.c文件中找到。 **核心代码如下** ````c void send_ac_power_on(void) { printf("准备发送空调开机信号\n"); len = IrSend(buf, 0); printf("IrSend 返回长度: %d\n", len); int write_result = uapi_uart_write(CONFIG_UART_BUS_ID, buf, len, 1000); printf("uapi_uart_write 返回结果: %d\n", write_result); printf("空调开机信号发送完成\n"); PRINT("空调打开\n", 0); } void send_ac_power_off(void) { len = IrSend(buf, 1); PRINT("空调关闭\n", 1); uapi_uart_write(CONFIG_UART_BUS_ID, buf, len, 1000); } ```` ## 2.3 Server端2-智能插座控制 ### 2.3.1 智能插座控制-硬件实现 1.BearPi-Pico_H3863核心板x1

2.继电器模块x1

3.整体接线图

### 2.3.2 智能插座控制-软件实现 该Server端的核心是**GPIO**,接收到来自Client端的星闪信号后,H3863会通过控制GPIO的电平高低对继电器进行控制,通过继电器实现外接插座的通电与断电。 **核心部分代码如下** ````c // 打开继电器 void relay_on(void) { // 设置继电器引脚为高电平 uapi_gpio_set_val(RELAY_PIN, GPIO_LEVEL_HIGH); relay_state = true; } // 关闭继电器 void relay_off(void) { // 设置继电器引脚为低电平 uapi_gpio_set_val(RELAY_PIN, GPIO_LEVEL_LOW); relay_state = false; } // 切换继电器状态 void relay_toggle(void) { if (relay_state) { relay_off(); } else { relay_on(); } } // 获取继电器状态 bool relay_get_state(void) { return relay_state; } ```` ## 2.4 Server端3-智能灯光控制 ### 2.4.1 智能灯光控制-硬件实现 1.HiHope_NearLink_DK3863E_V03核心板x1

2.SG90舵机x1

3.整体接线图

### 2.4.2 智能灯光控制-软件实现 该Server端的核心是**舵机驱动**,接收到来自Client端的星闪信号后,WS63E会通过控制GPIO的电平高低模拟PWM波对舵机进行控制,通过舵机旋转实现灯光开关的控制。 **核心部分代码如下** ````c // 舵机驱动 void S92RInit(void) { uapi_pin_set_mode(BSP_SG92R, HAL_PIO_FUNC_GPIO); uapi_gpio_set_dir(BSP_SG92R, GPIO_DIRECTION_OUTPUT); uapi_gpio_set_val(BSP_SG92R, GPIO_LEVEL_LOW); } void SetAngle(unsigned int duty) { unsigned int time = FREQ_TIME; uapi_gpio_set_val(BSP_SG92R, GPIO_LEVEL_HIGH); uapi_systick_delay_us(duty); uapi_gpio_set_val(BSP_SG92R, GPIO_LEVEL_LOW); uapi_systick_delay_us(time - duty); } /* The steering gear is centered * 1、依据角度与脉冲的关系,设置高电平时间为1500微秒 * 2、不断地发送信号,控制舵机居中 */ void RegressMiddle(void) { unsigned int angle = 1500; for (int i = 0; i < COUNT; i++) { SetAngle(angle); } } /* Turn 90 degrees to the right of the steering gear * 1、依据角度与脉冲的关系,设置高电平时间为500微秒 * 2、不断地发送信号,控制舵机向右旋转90度 */ /* Steering gear turn right */ void EngineTurnRight(void) { unsigned int angle = 500; for (int i = 0; i < COUNT; i++) { SetAngle(angle); } } /* Turn 90 degrees to the left of the steering gear * 1、依据角度与脉冲的关系,设置高电平时间为2500微秒 * 2、不断地发送信号,控制舵机向左旋转90度 */ /* Steering gear turn left */ void EngineTurnLeft(void) { unsigned int angle = 2500; for (int i = 0; i < COUNT; i++) { SetAngle(angle); } } ```` **注:所有Server端address要提前预设好,并且不能重复** # 3 作品部署指南 - 请参见 本作品采用 SDK 仓库中的 README 文件。 ### 作者 **73号星闪体验官-杨 阳** **email: ** ### 版权说明 该项目签署了MIT 授权许可,详情请参阅 [LICENSE.txt](https://github.com/shaojintian/Best_README_template/blob/master/LICENSE.txt) ### 鸣谢 **梅科尔工作室 MakerStudio** [license-shield]: https://img.shields.io/github/license/shaojintian/Best_README_template.svg?style=flat-square [license-url]: https://github.com/shaojintian/Best_README_template/blob/master/LICENSE.txt