# 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