```
feat(bluetooth): 添加多按钮支持和WiFi连接音频反馈 添加MultiButton库支持多按键功能,重构SPI显示屏驱动代码, 迁移MP3音频文件至正确目录并集成WiFi连接状态音频提示音。 - 添加Multi_Button.c源文件和相关头文件包含 - 重构spi_st7735s.c中的数组初始化格式,优化代码可读性 - 将MP3音频文件从Development_Docs/MP3迁移到Core/Bsp/BSP_Device/bsp_mp3/MP3 - 在WiFi连接过程中添加MP3音频反馈(连接成功/失败提示音) - 优化ST7735显示屏驱动中的DMA传输模式支持 ```
This commit is contained in:
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0001.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0001.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0002.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0002.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0003.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0003.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0004.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0004.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0005.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0005.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0006.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0006.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0007.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0007.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0008.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0008.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0009.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0009.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0010.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0010.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0011.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0011.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0012.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0012.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0013.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0013.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0014.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0014.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0015.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0015.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0016.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0016.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0017.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0017.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0018.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0018.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0019.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0019.mp3
Normal file
Binary file not shown.
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0020.mp3
Normal file
BIN
Core/Bsp/BSP_Device/bsp_mp3/MP3/0020.mp3
Normal file
Binary file not shown.
@@ -0,0 +1,81 @@
|
||||
# MP3语音播放集成总结
|
||||
|
||||
## 语音文件列表
|
||||
|
||||
| 编号 | 文件名 | 语音内容 | 触发场景 |
|
||||
|------|--------|----------|----------|
|
||||
| 001 | 001.mp3 | 智能宠物喂食系统启动完成,进入待机模式 | 系统上电完成 |
|
||||
| 002 | 002.mp3 | WiFi连接成功,云平台数据同步已开启 | WiFi连接成功 |
|
||||
| 003 | 003.mp3 | WiFi连接失败,请检查网络配置 | WiFi连接失败 |
|
||||
| 004 | 004.mp3 | 自动喂食模式启动,步进电机开始出粮 | 自动喂食启动 |
|
||||
| 005 | 005.mp3 | 正在出粮,称重模块实时监测中 | 喂食进行中 |
|
||||
| 006 | 006.mp3 | 喂食完成,当前食物重量已达设定值 | 喂食完成 |
|
||||
| 007 | 007.mp3 | 手动喂食指令已接收,开始出粮 | 手动喂食触发 |
|
||||
| 008 | 008.mp3 | 警告:食物余量低于下限,请及时添加 | 食物余量不足 |
|
||||
| 009 | 009.mp3 | 水位低于阈值,水泵启动,开始自动补水 | 自动补水启动 |
|
||||
| 010 | 010.mp3 | 补水完成,水位已达设定上限 | 补水完成 |
|
||||
| 011 | 011.mp3 | 警告:水位过低,请检查水源或水泵 | 水位过低 |
|
||||
| 012 | 012.mp3 | 检测到宠物靠近,水位偏低,启动自动补水 | PIR联动补水 |
|
||||
| 013 | 013.mp3 | 已切换至自动运行模式 | 切换自动模式 |
|
||||
| 014 | 014.mp3 | 已切换至手动控制模式 | 切换手动模式 |
|
||||
| 015 | 015.mp3 | 参数设置已保存,系统配置已更新 | 参数设置成功 |
|
||||
| 016 | 016.mp3 | 自动喂食时间已更新 | 定时时间修改 |
|
||||
| 017 | 017.mp3 | 喂食重量阈值已更新 | 重量阈值修改 |
|
||||
| 018 | 018.mp3 | 接收到微信小程序远程控制指令 | 远程指令接收 |
|
||||
| 019 | 019.mp3 | 数据上传失败,请检查网络连接 | 数据上传失败 |
|
||||
| 020 | 020.mp3 | 系统检测到异常,请检查硬件模块 | 系统异常 |
|
||||
|
||||
**说明:**
|
||||
- 编号从001开始顺序排列
|
||||
- 文件名与编号对应(001.mp3、002.mp3...)
|
||||
- 每项包含:编号、文件名、语音内容、触发场景四列信息
|
||||
- 共20条语音文件,覆盖系统所有核心功能模块
|
||||
|
||||
|
||||
## MP3驱动API
|
||||
|
||||
### 初始化函数
|
||||
```c
|
||||
HAL_StatusTypeDef MP3_Init(void);
|
||||
```
|
||||
- 初始化MP3模块
|
||||
- 设置音量30,选择TF卡音源
|
||||
|
||||
### 播放控制
|
||||
```c
|
||||
HAL_StatusTypeDef MP3_Play(uint16_t index); // 播放指定曲目 (1-9999)
|
||||
HAL_StatusTypeDef MP3_Stop(void); // 停止播放
|
||||
HAL_StatusTypeDef MP3_Pause(void); // 暂停播放
|
||||
```
|
||||
|
||||
### 参数设置
|
||||
```c
|
||||
HAL_StatusTypeDef MP3_SetVolume(uint8_t volume); // 设置音量 (0-30)
|
||||
HAL_StatusTypeDef MP3_SetSource(uint8_t source); // 设置音源 (1=U盘, 2=SD卡)
|
||||
```
|
||||
|
||||
## MP3命令格式
|
||||
|
||||
10字节固定格式:
|
||||
```
|
||||
字节0: 0x7E 帧头
|
||||
字节1: 0xFF 版本号
|
||||
字节2: 0x06 数据长度
|
||||
字节3: 命令码 (0x06=音量, 0x09=音源, 0x12=播放索引)
|
||||
字节4: 0x00 反馈
|
||||
字节5: 数据高字节
|
||||
字节6: 数据低字节
|
||||
字节7: 校验和高位
|
||||
字节8: 校验和低位
|
||||
字节9: 0xEF 帧尾
|
||||
```
|
||||
|
||||
校验和算法: `0x10000 - sum(字节1~6)`
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保TF卡中音频文件命名为001.mp3, 002.mp3, ... 格式
|
||||
2. MP3模块需等待2秒上电稳定
|
||||
3. USART3波特率设置为9600
|
||||
4. 首次播放建议测试`MP3_Play(1)`是否正常工作
|
||||
5. 所有语音播放函数已集成到相应按键处理逻辑中
|
||||
209
Core/Bsp/BSP_Device/bsp_mp3/MP3/LIST/mp3_usage_example.py
Normal file
209
Core/Bsp/BSP_Device/bsp_mp3/MP3/LIST/mp3_usage_example.py
Normal file
@@ -0,0 +1,209 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
MP3播放函数使用示例
|
||||
展示如何在代码中使用封装好的MP3播放函数
|
||||
"""
|
||||
|
||||
# ==========================================
|
||||
# C代码使用示例
|
||||
# ==========================================
|
||||
|
||||
example_code = """
|
||||
// 在 freertos.c 中使用 MP3 播放函数
|
||||
|
||||
#include "mp3_driver.h"
|
||||
|
||||
// ==========================================
|
||||
// 1. 初始化MP3模块 (在 MP3 任务中)
|
||||
// ==========================================
|
||||
void MP3(void *argument) {
|
||||
// 等待模块上电稳定
|
||||
osDelay(2000);
|
||||
|
||||
// 初始化MP3模块
|
||||
HAL_StatusTypeDef ret = MP3_Init();
|
||||
if (ret != HAL_OK) {
|
||||
elog_e("MP3", "MP3模块初始化失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// ... 其他代码
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 2. 播放指定曲目
|
||||
// ==========================================
|
||||
// 在 Sensor 任务中播放语音
|
||||
MP3_Play(1); // 播放第1首: "欢迎使用智能按摩器"
|
||||
MP3_Play(2); // 播放第2首: "定时10分钟"
|
||||
MP3_Play(3); // 播放第3首: "定时20分钟"
|
||||
// ... 依此类推
|
||||
|
||||
// ==========================================
|
||||
// 3. 其他常用函数
|
||||
// ==========================================
|
||||
MP3_Stop(); // 停止播放
|
||||
MP3_Pause(); // 暂停播放
|
||||
MP3_SetVolume(20); // 设置音量 (0-30)
|
||||
MP3_SetSource(MP3_SOURCE_SD_CARD); // 选择TF卡音源
|
||||
|
||||
// ==========================================
|
||||
// 4. 实际应用示例:时间设定
|
||||
// ==========================================
|
||||
if (timer_minutes == 10) {
|
||||
MP3_Play(2); // "定时10分钟"
|
||||
} else if (timer_minutes == 20) {
|
||||
MP3_Play(3); // "定时20分钟"
|
||||
} else if (timer_minutes == 30) {
|
||||
MP3_Play(4); // "定时30分钟"
|
||||
} else if (timer_minutes == 0) {
|
||||
MP3_Play(5); // "定时已取消"
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 5. 实际应用示例:档位控制
|
||||
// ==========================================
|
||||
if (current_gear == 1) {
|
||||
MP3_Play(8); // "一档"
|
||||
} else if (current_gear == 2) {
|
||||
MP3_Play(9); // "二档"
|
||||
} else if (current_gear == 3) {
|
||||
MP3_Play(10); // "三档"
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 6. 实际应用示例:加热控制
|
||||
// ==========================================
|
||||
if (hot_state) {
|
||||
MP3_Play(15); // "加热已开启"
|
||||
} else {
|
||||
MP3_Play(16); // "加热已关闭"
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 7. 实际应用示例:定时结束
|
||||
// ==========================================
|
||||
if (remaining_seconds == 0) {
|
||||
MP3_Play(18); // "定时结束,按摩结束"
|
||||
Motor_SetGear(0); // 停止电机
|
||||
// ... 其他清理工作
|
||||
}
|
||||
"""
|
||||
|
||||
# ==========================================
|
||||
# 语音文件映射表
|
||||
# ==========================================
|
||||
|
||||
voice_mapping = """
|
||||
+------+--------------------------+
|
||||
| 编号 | 语音内容 |
|
||||
+------+--------------------------+
|
||||
| 001 | 欢迎使用智能按摩器 |
|
||||
| 002 | 定时10分钟 |
|
||||
| 003 | 定时20分钟 |
|
||||
| 004 | 定时30分钟 |
|
||||
| 005 | 定时已取消 |
|
||||
| 006 | 按摩已停止 |
|
||||
| 007 | 按摩开始 |
|
||||
| 008 | 一档 |
|
||||
| 009 | 二档 |
|
||||
| 010 | 三档 |
|
||||
| 011 | 已到最大档位 (可选) |
|
||||
| 012 | 一档 (降档用) |
|
||||
| 013 | 二档 (降档用) |
|
||||
| 014 | 按摩停止 |
|
||||
| 015 | 加热已开启 |
|
||||
| 016 | 加热已关闭 |
|
||||
| 017 | 请先设定时间 |
|
||||
| 018 | 定时结束,按摩结束 |
|
||||
+------+--------------------------+
|
||||
"""
|
||||
|
||||
# ==========================================
|
||||
# MP3命令格式说明
|
||||
# ==========================================
|
||||
|
||||
command_format = """
|
||||
MP3命令格式 (10字节):
|
||||
====================================================
|
||||
字节 | 内容 | 说明
|
||||
-----+---------------+---------------------------
|
||||
0 | 0x7E | 帧头 (固定)
|
||||
1 | 0xFF | 版本号 (固定)
|
||||
2 | 0x06 | 数据长度 (固定)
|
||||
3 | 命令码 | 0x06=音量, 0x09=音源, 0x12=播放索引
|
||||
4 | 0x00 | 反馈 (固定)
|
||||
5 | 数据高字节 | 根据命令不同
|
||||
6 | 数据低字节 | 根据命令不同
|
||||
7 | 校验和高位 | 计算得出
|
||||
8 | 校验和低位 | 计算得出
|
||||
9 | 0xEF | 帧尾 (固定)
|
||||
====================================================
|
||||
|
||||
校验和计算方法:
|
||||
1. 从索引1 (0xFF) 开始累加到索引6 (数据低字节)
|
||||
2. 用 0x10000 减去累加结果
|
||||
3. 高位在前,低位在后
|
||||
|
||||
示例 (播放文件1):
|
||||
数据: 0xFF 0x06 0x12 0x00 0x00 0x01
|
||||
求和: 0xFF + 0x06 + 0x12 + 0x00 + 0x00 + 0x01 = 0x0118
|
||||
校验: 0x10000 - 0x0118 = 0xFEE8
|
||||
高位: 0xFE, 低位: 0xE8
|
||||
|
||||
完整命令: 0x7E 0xFF 0x06 0x12 0x00 0x00 0x01 0xFE 0xE8 0xEF
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 70)
|
||||
print("MP3播放函数使用示例".center(70))
|
||||
print("=" * 70)
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("1. C代码使用示例")
|
||||
print("=" * 70)
|
||||
print(example_code)
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("2. 语音文件映射表")
|
||||
print("=" * 70)
|
||||
print(voice_mapping)
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("3. MP3命令格式说明")
|
||||
print("=" * 70)
|
||||
print(command_format)
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("4. API 函数列表")
|
||||
print("=" * 70)
|
||||
print("""
|
||||
HAL_StatusTypeDef MP3_Init(void);
|
||||
- 初始化MP3模块
|
||||
- 返回: HAL_OK(成功) 或 HAL_ERROR(失败)
|
||||
|
||||
HAL_StatusTypeDef MP3_Play(uint16_t index);
|
||||
- 播放指定曲目 (索引范围: 1-9999)
|
||||
- 参数: index - 曲目编号
|
||||
- 返回: HAL_OK(成功) 或 HAL_ERROR(失败)
|
||||
|
||||
HAL_StatusTypeDef MP3_Stop(void);
|
||||
- 停止当前播放
|
||||
- 返回: HAL_OK(成功) 或 HAL_ERROR(失败)
|
||||
|
||||
HAL_StatusTypeDef MP3_Pause(void);
|
||||
- 暂停当前播放
|
||||
- 返回: HAL_OK(成功) 或 HAL_ERROR(失败)
|
||||
|
||||
HAL_StatusTypeDef MP3_SetVolume(uint8_t volume);
|
||||
- 设置音量 (范围: 0-30)
|
||||
- 参数: volume - 音量值
|
||||
- 返回: HAL_OK(成功) 或 HAL_ERROR(失败)
|
||||
|
||||
HAL_StatusTypeDef MP3_SetSource(uint8_t source);
|
||||
- 设置音源
|
||||
- 参数: source - MP3_SOURCE_U_DISK(1) 或 MP3_SOURCE_SD_CARD(2)
|
||||
- 返回: HAL_OK(成功) 或 HAL_ERROR(失败)
|
||||
""")
|
||||
print("=" * 70)
|
||||
222
Core/Bsp/BSP_Device/bsp_mp3/MP3/LIST/rename_mp3_files.py
Normal file
222
Core/Bsp/BSP_Device/bsp_mp3/MP3/LIST/rename_mp3_files.py
Normal file
@@ -0,0 +1,222 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
音频文件按时间戳排序并重命名工具
|
||||
功能:
|
||||
1. 查找指定路径下的MP3文件
|
||||
2. 按文件创建时间从先到后排序
|
||||
3. 显示排序后的文件列表
|
||||
4. 用户可指定起始编号(如0001或0002)
|
||||
5. 重命名文件为0001.mp3, 0002.mp3等格式
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import glob
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def find_mp3_files(directory):
|
||||
"""查找指定目录下的所有MP3文件"""
|
||||
mp3_files = glob.glob(os.path.join(directory, "*.mp3"))
|
||||
return mp3_files
|
||||
|
||||
|
||||
def is_timestamp_filename(filename):
|
||||
"""判断文件名是否为时间戳格式(纯数字.mp3)"""
|
||||
# 去除扩展名
|
||||
name_without_ext = os.path.splitext(filename)[0]
|
||||
# 检查是否为纯数字且至少为10位(时间戳)
|
||||
return name_without_ext.isdigit() and len(name_without_ext) >= 10
|
||||
|
||||
|
||||
def is_numbered_filename(filename):
|
||||
"""判断文件名是否为编号格式(如0001.mp3)"""
|
||||
# 去除扩展名
|
||||
name_without_ext = os.path.splitext(filename)[0]
|
||||
# 检查是否为纯数字,长度为3-4位(编号格式)
|
||||
return name_without_ext.isdigit() and len(name_without_ext) >= 3 and len(name_without_ext) <= 4
|
||||
|
||||
|
||||
def filter_timestamp_files(files):
|
||||
"""筛选出时间戳格式的MP3文件,跳过编号格式的文件"""
|
||||
timestamp_files = []
|
||||
skipped_files = []
|
||||
|
||||
for file_path in files:
|
||||
filename = os.path.basename(file_path)
|
||||
if is_numbered_filename(filename):
|
||||
skipped_files.append(filename)
|
||||
elif is_timestamp_filename(filename):
|
||||
timestamp_files.append(file_path)
|
||||
else:
|
||||
skipped_files.append(filename)
|
||||
|
||||
return timestamp_files, skipped_files
|
||||
|
||||
|
||||
def sort_files_by_time(files):
|
||||
"""按文件创建时间从先到后排序"""
|
||||
# 获取文件信息并按创建时间排序
|
||||
file_info = []
|
||||
for file_path in files:
|
||||
stat = os.stat(file_path)
|
||||
file_info.append({
|
||||
'path': file_path,
|
||||
'name': os.path.basename(file_path),
|
||||
'ctime': stat.st_ctime # 创建时间
|
||||
})
|
||||
|
||||
# 按创建时间升序排序
|
||||
sorted_files = sorted(file_info, key=lambda x: x['ctime'])
|
||||
return sorted_files
|
||||
|
||||
|
||||
def display_file_list(sorted_files, skipped_files):
|
||||
"""显示排序后的文件列表"""
|
||||
print("\n" + "=" * 80)
|
||||
if skipped_files:
|
||||
print(f"已跳过 {len(skipped_files)} 个非时间戳格式的文件:")
|
||||
for filename in skipped_files:
|
||||
print(f" 跳过: {filename}")
|
||||
print()
|
||||
|
||||
print(f"找到 {len(sorted_files)} 个时间戳格式的MP3文件,按创建时间排序:")
|
||||
print("=" * 80)
|
||||
for i, file_info in enumerate(sorted_files, 1):
|
||||
import time
|
||||
time_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(file_info['ctime']))
|
||||
print(f"{i:3d}. {file_info['name']:<50} 创建时间: {time_str}")
|
||||
print("=" * 80)
|
||||
|
||||
|
||||
def get_start_number():
|
||||
"""获取用户指定的起始编号"""
|
||||
while True:
|
||||
try:
|
||||
user_input = input("\n请输入起始编号(如1或2,将转换为0001/0002格式): ").strip()
|
||||
if not user_input:
|
||||
print("输入不能为空,请重新输入!")
|
||||
continue
|
||||
|
||||
start_num = int(user_input)
|
||||
if start_num < 1 or start_num > 9999:
|
||||
print("编号必须在1-9999之间,请重新输入!")
|
||||
continue
|
||||
|
||||
return start_num
|
||||
except ValueError:
|
||||
print("请输入有效的数字!")
|
||||
|
||||
|
||||
def rename_files(sorted_files, start_number):
|
||||
"""重命名文件为编号格式"""
|
||||
print("\n开始重命名文件...")
|
||||
print("=" * 80)
|
||||
|
||||
success_count = 0
|
||||
error_count = 0
|
||||
|
||||
for i, file_info in enumerate(sorted_files, start_number):
|
||||
old_path = file_info['path']
|
||||
directory = os.path.dirname(old_path)
|
||||
new_name = f"{i:04d}.mp3"
|
||||
new_path = os.path.join(directory, new_name)
|
||||
|
||||
try:
|
||||
os.rename(old_path, new_path)
|
||||
print(f"✓ {file_info['name']} -> {new_name}")
|
||||
success_count += 1
|
||||
except Exception as e:
|
||||
print(f"✗ {file_info['name']} 重命名失败: {e}")
|
||||
error_count += 1
|
||||
|
||||
print("=" * 80)
|
||||
print(f"重命名完成! 成功: {success_count}, 失败: {error_count}")
|
||||
|
||||
|
||||
def confirm_operation():
|
||||
"""确认是否执行重命名操作"""
|
||||
while True:
|
||||
user_input = input("\n确认要重命名这些文件吗? (y/n): ").strip().lower()
|
||||
if user_input in ['y', 'yes']:
|
||||
return True
|
||||
elif user_input in ['n', 'no']:
|
||||
return False
|
||||
else:
|
||||
print("请输入 y 或 n")
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 80)
|
||||
print("音频文件按时间戳排序并重命名工具".center(80))
|
||||
print("=" * 80)
|
||||
|
||||
# 获取目标目录
|
||||
while True:
|
||||
directory = input("\n请输入要处理的MP3文件所在路径: ").strip()
|
||||
directory = directory.strip('"').strip("'") # 去除可能的引号
|
||||
|
||||
if not directory:
|
||||
print("路径不能为空!")
|
||||
continue
|
||||
|
||||
if not os.path.isdir(directory):
|
||||
print(f"路径不存在: {directory}")
|
||||
continue
|
||||
|
||||
break
|
||||
|
||||
# 查找MP3文件
|
||||
mp3_files = find_mp3_files(directory)
|
||||
|
||||
if not mp3_files:
|
||||
print(f"\n在路径 {directory} 中未找到MP3文件!")
|
||||
return
|
||||
|
||||
# 筛选时间戳格式文件,跳过编号格式文件
|
||||
timestamp_files, skipped_files = filter_timestamp_files(mp3_files)
|
||||
|
||||
if not timestamp_files:
|
||||
print(f"\n在路径 {directory} 中未找到时间戳格式的MP3文件!")
|
||||
if skipped_files:
|
||||
print(f"但找到 {len(skipped_files)} 个已编号格式的文件:")
|
||||
for filename in skipped_files:
|
||||
print(f" {filename}")
|
||||
return
|
||||
|
||||
# 按时间排序
|
||||
sorted_files = sort_files_by_time(timestamp_files)
|
||||
|
||||
# 显示文件列表
|
||||
display_file_list(sorted_files, skipped_files)
|
||||
|
||||
# 获取起始编号
|
||||
start_number = get_start_number()
|
||||
|
||||
# 预览重命名结果
|
||||
print("\n重命名预览:")
|
||||
print("-" * 80)
|
||||
for i, file_info in enumerate(sorted_files, start_number):
|
||||
new_name = f"{i:04d}.mp3"
|
||||
print(f"{i:4d}. {file_info['name']} -> {new_name}")
|
||||
print("-" * 80)
|
||||
|
||||
# 确认操作
|
||||
if confirm_operation():
|
||||
rename_files(sorted_files, start_number)
|
||||
else:
|
||||
print("\n操作已取消!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n程序被用户中断!")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"\n程序执行出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
118
Core/Bsp/BSP_Device/bsp_mp3/MP3/LIST/verify_mp3_checksum.py
Normal file
118
Core/Bsp/BSP_Device/bsp_mp3/MP3/LIST/verify_mp3_checksum.py
Normal file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
MP3命令校验和验证工具
|
||||
验证校验和算法是否正确
|
||||
"""
|
||||
|
||||
def calc_checksum(cmd):
|
||||
"""
|
||||
计算MP3命令校验和
|
||||
算法:从版本号(索引1)开始,到数据结束,所有字节相加后取反再加1
|
||||
返回:高位和低位
|
||||
"""
|
||||
sum_val = 0
|
||||
|
||||
# 从版本号(索引1)开始累加,到数据结束(索引6)
|
||||
for i in range(1, 7):
|
||||
sum_val += cmd[i]
|
||||
|
||||
# 取反加1 (即0x10000 - sum_val)
|
||||
sum_val = 0x10000 - sum_val
|
||||
|
||||
# 高位在前,低位在后
|
||||
high = (sum_val >> 8) & 0xFF
|
||||
low = sum_val & 0xFF
|
||||
|
||||
return high, low
|
||||
|
||||
|
||||
def verify_command(cmd, name):
|
||||
"""验证命令的校验和是否正确"""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"验证命令: {name}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
# 提取原始数据
|
||||
data = cmd[:7]
|
||||
original_high = cmd[7]
|
||||
original_low = cmd[8]
|
||||
|
||||
# 显示命令字节
|
||||
print(f"命令字节: ", end="")
|
||||
for i, byte in enumerate(cmd):
|
||||
print(f"0x{byte:02X} ", end="")
|
||||
if i == 3:
|
||||
print("| ", end="")
|
||||
print()
|
||||
|
||||
# 显示数据部分
|
||||
print(f"数据部分 (索引1-6): ", end="")
|
||||
for i in range(1, 7):
|
||||
print(f"0x{cmd[i]:02X} ", end="")
|
||||
if i == 3:
|
||||
print("| ", end="")
|
||||
print()
|
||||
|
||||
# 显示求和过程
|
||||
sum_val = sum(cmd[1:7])
|
||||
print(f"\n求和: {sum_val} (0x{sum_val:04X})")
|
||||
print(f"取反加1: 0x10000 - 0x{sum_val:04X} = 0x{0x10000 - sum_val:04X}")
|
||||
|
||||
# 计算校验和
|
||||
calc_high, calc_low = calc_checksum(cmd)
|
||||
print(f"\n计算校验和: 高位=0x{calc_high:02X}, 低位=0x{calc_low:02X}")
|
||||
print(f"原始校验和: 高位=0x{original_high:02X}, 低位=0x{original_low:02X}")
|
||||
|
||||
# 验证
|
||||
if calc_high == original_high and calc_low == original_low:
|
||||
print("[OK] 校验和验证通过!")
|
||||
return True
|
||||
else:
|
||||
print("[FAIL] 校验和验证失败!")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
print("="*60)
|
||||
print("MP3命令校验和验证工具")
|
||||
print("="*60)
|
||||
|
||||
# 命令1:开启声音 (音量30)
|
||||
# 0x7E 0xFF 0x06 0x06 0x00 0x00 0x1E 0xFE 0xD7 0xEF
|
||||
cmd1 = [0x7E, 0xFF, 0x06, 0x06, 0x00, 0x00, 0x1E, 0xFE, 0xD7, 0xEF]
|
||||
verify_command(cmd1, "开启声音 (音量30)")
|
||||
|
||||
# 命令2:选择TF卡
|
||||
# 0x7E 0xFF 0x06 0x09 0x00 0x00 0x02 0xFE 0xF0 0xEF
|
||||
cmd2 = [0x7E, 0xFF, 0x06, 0x09, 0x00, 0x00, 0x02, 0xFE, 0xF0, 0xEF]
|
||||
verify_command(cmd2, "选择TF卡")
|
||||
|
||||
# 命令3:播放文件1
|
||||
# 0x7E 0xFF 0x06 0x12 0x00 0x00 0x01 0xFE 0xE8 0xEF
|
||||
cmd3 = [0x7E, 0xFF, 0x06, 0x12, 0x00, 0x00, 0x01, 0xFE, 0xE8, 0xEF]
|
||||
verify_command(cmd3, "播放文件1")
|
||||
|
||||
# 测试:生成其他曲目的播放命令
|
||||
print(f"\n{'='*60}")
|
||||
print("生成其他曲目的播放命令")
|
||||
print(f"{'='*60}")
|
||||
|
||||
for index in [1, 10, 100, 9999]:
|
||||
play_cmd = [0x7E, 0xFF, 0x06, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEF]
|
||||
play_cmd[5] = (index >> 8) & 0xFF
|
||||
play_cmd[6] = index & 0xFF
|
||||
high, low = calc_checksum(play_cmd)
|
||||
play_cmd[7] = high
|
||||
play_cmd[8] = low
|
||||
|
||||
print(f"\n曲目 {index:4d}: ", end="")
|
||||
for byte in play_cmd:
|
||||
print(f"0x{byte:02X} ", end="")
|
||||
print()
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -7,7 +7,7 @@ extern "C" {
|
||||
|
||||
#include "stm32f1xx_hal.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#include "mp3_play_index.h"
|
||||
/* MP3命令定义 */
|
||||
#define MP3_HEADER 0x7E // 帧头
|
||||
#define MP3_VERSION 0xFF // 版本号
|
||||
|
||||
@@ -6,117 +6,185 @@
|
||||
#define DELAY 0x80
|
||||
|
||||
// based on Adafruit ST7735 library for Arduino
|
||||
static const uint8_t
|
||||
init_cmds1[] = { // Init for 7735R, part 1 (red or green tab)
|
||||
15, // 15 commands in list:
|
||||
ST7735_SWRESET, DELAY, // 1: Software reset, 0 args, w/delay
|
||||
150, // 150 ms delay
|
||||
ST7735_SLPOUT, DELAY, // 2: Out of sleep mode, 0 args, w/delay
|
||||
255, // 500 ms delay
|
||||
ST7735_FRMCTR1, 3, // 3: Frame rate ctrl - normal mode, 3 args:
|
||||
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
|
||||
ST7735_FRMCTR2, 3, // 4: Frame rate control - idle mode, 3 args:
|
||||
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
|
||||
ST7735_FRMCTR3, 6, // 5: Frame rate ctrl - partial mode, 6 args:
|
||||
0x01, 0x2C, 0x2D, // Dot inversion mode
|
||||
0x01, 0x2C, 0x2D, // Line inversion mode
|
||||
ST7735_INVCTR, 1, // 6: Display inversion ctrl, 1 arg, no delay:
|
||||
0x07, // No inversion
|
||||
ST7735_PWCTR1, 3, // 7: Power control, 3 args, no delay:
|
||||
static const uint8_t init_cmds1[] =
|
||||
{ // Init for 7735R, part 1 (red or green tab)
|
||||
15, // 15 commands in list:
|
||||
ST7735_SWRESET,
|
||||
DELAY, // 1: Software reset, 0 args, w/delay
|
||||
150, // 150 ms delay
|
||||
ST7735_SLPOUT,
|
||||
DELAY, // 2: Out of sleep mode, 0 args, w/delay
|
||||
255, // 500 ms delay
|
||||
ST7735_FRMCTR1,
|
||||
3, // 3: Frame rate ctrl - normal mode, 3 args:
|
||||
0x01,
|
||||
0x2C,
|
||||
0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
|
||||
ST7735_FRMCTR2,
|
||||
3, // 4: Frame rate control - idle mode, 3 args:
|
||||
0x01,
|
||||
0x2C,
|
||||
0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
|
||||
ST7735_FRMCTR3,
|
||||
6, // 5: Frame rate ctrl - partial mode, 6 args:
|
||||
0x01,
|
||||
0x2C,
|
||||
0x2D, // Dot inversion mode
|
||||
0x01,
|
||||
0x2C,
|
||||
0x2D, // Line inversion mode
|
||||
ST7735_INVCTR,
|
||||
1, // 6: Display inversion ctrl, 1 arg, no delay:
|
||||
0x07, // No inversion
|
||||
ST7735_PWCTR1,
|
||||
3, // 7: Power control, 3 args, no delay:
|
||||
0xA2,
|
||||
0x02, // -4.6V
|
||||
0x84, // AUTO mode
|
||||
ST7735_PWCTR2, 1, // 8: Power control, 1 arg, no delay:
|
||||
0xC5, // VGH25 = 2.4C VGSEL = -10 VGH = 3 * AVDD
|
||||
ST7735_PWCTR3, 2, // 9: Power control, 2 args, no delay:
|
||||
0x0A, // Opamp current small
|
||||
0x00, // Boost frequency
|
||||
ST7735_PWCTR4, 2, // 10: Power control, 2 args, no delay:
|
||||
0x8A, // BCLK/2, Opamp current small & Medium low
|
||||
0x02, // -4.6V
|
||||
0x84, // AUTO mode
|
||||
ST7735_PWCTR2,
|
||||
1, // 8: Power control, 1 arg, no delay:
|
||||
0xC5, // VGH25 = 2.4C VGSEL = -10 VGH = 3 * AVDD
|
||||
ST7735_PWCTR3,
|
||||
2, // 9: Power control, 2 args, no delay:
|
||||
0x0A, // Opamp current small
|
||||
0x00, // Boost frequency
|
||||
ST7735_PWCTR4,
|
||||
2, // 10: Power control, 2 args, no delay:
|
||||
0x8A, // BCLK/2, Opamp current small & Medium low
|
||||
0x2A,
|
||||
ST7735_PWCTR5, 2, // 11: Power control, 2 args, no delay:
|
||||
0x8A, 0xEE,
|
||||
ST7735_VMCTR1, 1, // 12: Power control, 1 arg, no delay:
|
||||
ST7735_PWCTR5,
|
||||
2, // 11: Power control, 2 args, no delay:
|
||||
0x8A,
|
||||
0xEE,
|
||||
ST7735_VMCTR1,
|
||||
1, // 12: Power control, 1 arg, no delay:
|
||||
0x0E,
|
||||
ST7735_INVOFF, 0, // 13: Don't invert display, no args, no delay
|
||||
ST7735_MADCTL, 1, // 14: Memory access control (directions), 1 arg:
|
||||
ST7735_ROTATION, // row addr/col addr, bottom to top refresh
|
||||
ST7735_COLMOD, 1, // 15: set color mode, 1 arg, no delay:
|
||||
0x05}, // 16-bit color
|
||||
ST7735_INVOFF,
|
||||
0, // 13: Don't invert display, no args, no delay
|
||||
ST7735_MADCTL,
|
||||
1, // 14: Memory access control (directions), 1 arg:
|
||||
ST7735_ROTATION, // row addr/col addr, bottom to top refresh
|
||||
ST7735_COLMOD,
|
||||
1, // 15: set color mode, 1 arg, no delay:
|
||||
0x05}, // 16-bit color
|
||||
|
||||
#if (defined(ST7735_IS_128X128) || defined(ST7735_IS_160X128))
|
||||
init_cmds2[] = { // Init for 7735R, part 2 (1.44" display)
|
||||
2, // 2 commands in list:
|
||||
ST7735_CASET, 4, // 1: Column addr set, 4 args, no delay:
|
||||
0x00, 0x00, // XSTART = 0
|
||||
0x00, 0x7F, // XEND = 127
|
||||
ST7735_RASET, 4, // 2: Row addr set, 4 args, no delay:
|
||||
0x00, 0x00, // XSTART = 0
|
||||
0x00, 0x7F}, // XEND = 127
|
||||
#endif // ST7735_IS_128X128
|
||||
init_cmds2[] =
|
||||
{ // Init for 7735R, part 2 (1.44" display)
|
||||
2, // 2 commands in list:
|
||||
ST7735_CASET,
|
||||
4, // 1: Column addr set, 4 args, no delay:
|
||||
0x00,
|
||||
0x00, // XSTART = 0
|
||||
0x00,
|
||||
0x7F, // XEND = 127
|
||||
ST7735_RASET,
|
||||
4, // 2: Row addr set, 4 args, no delay:
|
||||
0x00,
|
||||
0x00, // XSTART = 0
|
||||
0x00,
|
||||
0x7F}, // XEND = 127
|
||||
#endif // ST7735_IS_128X128
|
||||
|
||||
#ifdef ST7735_IS_160X80
|
||||
init_cmds2[] = { // Init for 7735S, part 2 (160x80 display)
|
||||
3, // 3 commands in list:
|
||||
ST7735_CASET, 4, // 1: Column addr set, 4 args, no delay:
|
||||
0x00, 0x00, // XSTART = 0
|
||||
0x00, 0x4F, // XEND = 79
|
||||
ST7735_RASET, 4, // 2: Row addr set, 4 args, no delay:
|
||||
0x00, 0x00, // XSTART = 0
|
||||
0x00, 0x9F, // XEND = 159
|
||||
ST7735_INVON, 0}, // 3: Invert colors
|
||||
init_cmds2[] =
|
||||
{ // Init for 7735S, part 2 (160x80 display)
|
||||
3, // 3 commands in list:
|
||||
ST7735_CASET,
|
||||
4, // 1: Column addr set, 4 args, no delay:
|
||||
0x00,
|
||||
0x00, // XSTART = 0
|
||||
0x00,
|
||||
0x4F, // XEND = 79
|
||||
ST7735_RASET,
|
||||
4, // 2: Row addr set, 4 args, no delay:
|
||||
0x00,
|
||||
0x00, // XSTART = 0
|
||||
0x00,
|
||||
0x9F, // XEND = 159
|
||||
ST7735_INVON,
|
||||
0}, // 3: Invert colors
|
||||
#endif
|
||||
|
||||
init_cmds3[] = { // Init for 7735R, part 3 (red or green tab)
|
||||
4, // 4 commands in list:
|
||||
ST7735_GMCTRP1, 16, // 1: Gamma Adjustments (pos. polarity), 16 args, no delay:
|
||||
0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10, ST7735_GMCTRN1, 16, // 2: Gamma Adjustments (neg. polarity), 16 args, no delay:
|
||||
0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10, ST7735_NORON, DELAY, // 3: Normal display on, no args, w/delay
|
||||
10, // 10 ms delay
|
||||
ST7735_DISPON, DELAY, // 4: Main screen turn on, no args w/delay
|
||||
100}; // 100 ms delay
|
||||
init_cmds3[] = { // Init for 7735R, part 3 (red or green tab)
|
||||
4, // 4 commands in list:
|
||||
ST7735_GMCTRP1,
|
||||
16, // 1: Gamma Adjustments (pos. polarity), 16 args, no delay:
|
||||
0x02,
|
||||
0x1c,
|
||||
0x07,
|
||||
0x12,
|
||||
0x37,
|
||||
0x32,
|
||||
0x29,
|
||||
0x2d,
|
||||
0x29,
|
||||
0x25,
|
||||
0x2B,
|
||||
0x39,
|
||||
0x00,
|
||||
0x01,
|
||||
0x03,
|
||||
0x10,
|
||||
ST7735_GMCTRN1,
|
||||
16, // 2: Gamma Adjustments (neg. polarity), 16 args, no delay:
|
||||
0x03,
|
||||
0x1d,
|
||||
0x07,
|
||||
0x06,
|
||||
0x2E,
|
||||
0x2C,
|
||||
0x29,
|
||||
0x2D,
|
||||
0x2E,
|
||||
0x2E,
|
||||
0x37,
|
||||
0x3F,
|
||||
0x00,
|
||||
0x00,
|
||||
0x02,
|
||||
0x10,
|
||||
ST7735_NORON,
|
||||
DELAY, // 3: Normal display on, no args, w/delay
|
||||
10, // 10 ms delay
|
||||
ST7735_DISPON,
|
||||
DELAY, // 4: Main screen turn on, no args w/delay
|
||||
100}; // 100 ms delay
|
||||
|
||||
static void ST7735_Select()
|
||||
{
|
||||
static void ST7735_Select() {
|
||||
HAL_GPIO_WritePin(ST7735_CS_GPIO_Port, ST7735_CS_Pin, GPIO_PIN_RESET);
|
||||
}
|
||||
|
||||
void ST7735_Unselect()
|
||||
{
|
||||
void ST7735_Unselect() {
|
||||
HAL_GPIO_WritePin(ST7735_CS_GPIO_Port, ST7735_CS_Pin, GPIO_PIN_SET);
|
||||
}
|
||||
|
||||
static void ST7735_Reset()
|
||||
{
|
||||
static void ST7735_Reset() {
|
||||
HAL_GPIO_WritePin(ST7735_RES_GPIO_Port, ST7735_RES_Pin, GPIO_PIN_RESET);
|
||||
HAL_Delay(5);
|
||||
HAL_GPIO_WritePin(ST7735_RES_GPIO_Port, ST7735_RES_Pin, GPIO_PIN_SET);
|
||||
}
|
||||
|
||||
static void ST7735_WriteCommand(uint8_t cmd)
|
||||
{
|
||||
static void ST7735_WriteCommand(uint8_t cmd) {
|
||||
HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_RESET);
|
||||
|
||||
HAL_SPI_Transmit(&ST7735_SPI_PORT, &cmd, sizeof(cmd), HAL_MAX_DELAY);
|
||||
// HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, &cmd, sizeof(cmd));
|
||||
// HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
static void ST7735_WriteData(uint8_t *buff, size_t buff_size)
|
||||
{
|
||||
static void ST7735_WriteData(uint8_t *buff, size_t buff_size) {
|
||||
HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_SET);
|
||||
|
||||
HAL_SPI_Transmit(&ST7735_SPI_PORT, buff, buff_size, HAL_MAX_DELAY);
|
||||
// HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, buff, buff_size);
|
||||
HAL_SPI_Transmit(&ST7735_SPI_PORT, buff, buff_size, HAL_MAX_DELAY);
|
||||
// HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, buff, buff_size);
|
||||
}
|
||||
|
||||
static void ST7735_ExecuteCommandList(const uint8_t *addr)
|
||||
{
|
||||
static void ST7735_ExecuteCommandList(const uint8_t *addr) {
|
||||
uint8_t numCommands, numArgs;
|
||||
uint16_t ms;
|
||||
|
||||
numCommands = *addr++;
|
||||
while (numCommands--)
|
||||
{
|
||||
while (numCommands--) {
|
||||
uint8_t cmd = *addr++;
|
||||
ST7735_WriteCommand(cmd);
|
||||
|
||||
@@ -124,14 +192,12 @@ static void ST7735_ExecuteCommandList(const uint8_t *addr)
|
||||
// If high bit set, delay follows args
|
||||
ms = numArgs & DELAY;
|
||||
numArgs &= ~DELAY;
|
||||
if (numArgs)
|
||||
{
|
||||
if (numArgs) {
|
||||
ST7735_WriteData((uint8_t *)addr, numArgs);
|
||||
addr += numArgs;
|
||||
}
|
||||
|
||||
if (ms)
|
||||
{
|
||||
if (ms) {
|
||||
ms = *addr++;
|
||||
if (ms == 255)
|
||||
ms = 500;
|
||||
@@ -151,8 +217,8 @@ static void ST7735_ExecuteCommandList(const uint8_t *addr)
|
||||
* @param x1 显示窗口右下角的X坐标
|
||||
* @param y1 显示窗口右下角的Y坐标
|
||||
*/
|
||||
static void ST7735_SetAddressWindow(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1)
|
||||
{
|
||||
static void ST7735_SetAddressWindow(uint8_t x0, uint8_t y0, uint8_t x1,
|
||||
uint8_t y1) {
|
||||
// column address set
|
||||
ST7735_WriteCommand(ST7735_CASET);
|
||||
// 设置列地址范围,考虑到显示区域的起始偏移
|
||||
@@ -178,8 +244,7 @@ static void ST7735_SetAddressWindow(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t
|
||||
* 以及初始化完成后填充屏幕为黄色。这些操作确保了显示器能够正确配置并进入
|
||||
* 可显示状态。
|
||||
*/
|
||||
void ST7735_Init()
|
||||
{
|
||||
void ST7735_Init() {
|
||||
// 选择ST7735设备,准备进行初始化
|
||||
ST7735_Select();
|
||||
|
||||
@@ -216,8 +281,7 @@ void ST7735_Init()
|
||||
* @param y 纵坐标,表示像素点在显示屏上的垂直位置。
|
||||
* @param color 像素点的颜色值,采用RGB565格式。
|
||||
*/
|
||||
void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color)
|
||||
{
|
||||
void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color) {
|
||||
// 检查坐标是否超出显示屏边界
|
||||
if ((x >= ST7735_WIDTH) || (y >= ST7735_HEIGHT))
|
||||
return;
|
||||
@@ -232,13 +296,13 @@ void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color)
|
||||
uint8_t data[] = {color >> 8, color & 0xFF};
|
||||
|
||||
// 发送颜色数据到显示屏
|
||||
//ST7735_WriteData(data, sizeof(data));
|
||||
// ST7735_WriteData(data, sizeof(data));
|
||||
|
||||
HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_SET);
|
||||
|
||||
HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, data, sizeof(data));
|
||||
|
||||
// HAL_SPI_Transmit(&ST7735_SPI_PORT, data, sizeof(data), HAL_MAX_DELAY);
|
||||
// HAL_SPI_Transmit(&ST7735_SPI_PORT, data, sizeof(data), HAL_MAX_DELAY);
|
||||
|
||||
// 取消选择显示屏,结束操作
|
||||
ST7735_Unselect();
|
||||
@@ -254,8 +318,8 @@ void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color)
|
||||
* @param color 字符的颜色
|
||||
* @param bgcolor 背景颜色
|
||||
*/
|
||||
static void ST7735_WriteChar(uint16_t x, uint16_t y, char ch, FontDef *font, uint16_t color, uint16_t bgcolor)
|
||||
{
|
||||
static void ST7735_WriteChar(uint16_t x, uint16_t y, char ch, FontDef *font,
|
||||
uint16_t color, uint16_t bgcolor) {
|
||||
// 定义循环变量和临时变量
|
||||
uint32_t i, b, j;
|
||||
|
||||
@@ -263,25 +327,24 @@ static void ST7735_WriteChar(uint16_t x, uint16_t y, char ch, FontDef *font, uin
|
||||
ST7735_SetAddressWindow(x, y, x + font->width - 1, y + font->height - 1);
|
||||
|
||||
// 遍历字符的每一行
|
||||
for (i = 0; i < font->height; i++)
|
||||
{
|
||||
for (i = 0; i < font->height; i++) {
|
||||
// 获取当前行的字形数据
|
||||
b = font->data[(ch - 32) * font->height + i];
|
||||
// 遍历当前行的每一列
|
||||
for (j = 0; j < font->width; j++)
|
||||
{
|
||||
for (j = 0; j < font->width; j++) {
|
||||
// 检查当前列是否为前景色
|
||||
if ((b << j) & 0x8000)
|
||||
{
|
||||
if ((b << j) & 0x8000) {
|
||||
// 是前景色,则发送颜色数据到显示屏
|
||||
uint8_t data[] = {color >> 8, color & 0xFF};
|
||||
ST7735_WriteData(data, sizeof(data));
|
||||
}
|
||||
else
|
||||
{
|
||||
// ST7735_WriteData(data, sizeof(data));
|
||||
HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_SET);
|
||||
HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, data, sizeof(data));
|
||||
} else {
|
||||
// 不是前景色,则发送背景色数据到显示屏
|
||||
uint8_t data[] = {bgcolor >> 8, bgcolor & 0xFF};
|
||||
ST7735_WriteData(data, sizeof(data));
|
||||
// ST7735_WriteData(data, sizeof(data));
|
||||
HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_SET);
|
||||
HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, data, sizeof(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,8 +353,8 @@ static void ST7735_WriteChar(uint16_t x, uint16_t y, char ch, FontDef *font, uin
|
||||
/*
|
||||
Simpler (and probably slower) implementation:
|
||||
|
||||
static void ST7735_WriteChar(uint16_t x, uint16_t y, char ch, FontDef font, uint16_t color) {
|
||||
uint32_t i, b, j;
|
||||
static void ST7735_WriteChar(uint16_t x, uint16_t y, char ch, FontDef font,
|
||||
uint16_t color) { uint32_t i, b, j;
|
||||
|
||||
for(i = 0; i < font->height; i++) {
|
||||
b = font->data[(ch - 32) * font->height + i];
|
||||
@@ -304,23 +367,19 @@ static void ST7735_WriteChar(uint16_t x, uint16_t y, char ch, FontDef font, uint
|
||||
}
|
||||
*/
|
||||
|
||||
void ST7735_WriteString(uint16_t x, uint16_t y, const char *str, FontDef *font, uint16_t color, uint16_t bgcolor)
|
||||
{
|
||||
void ST7735_WriteString(uint16_t x, uint16_t y, const char *str, FontDef *font,
|
||||
uint16_t color, uint16_t bgcolor) {
|
||||
ST7735_Select();
|
||||
|
||||
while (*str)
|
||||
{
|
||||
if (x + font->width >= ST7735_WIDTH)
|
||||
{
|
||||
while (*str) {
|
||||
if (x + font->width >= ST7735_WIDTH) {
|
||||
x = 0;
|
||||
y += font->height;
|
||||
if (y + font->height >= ST7735_HEIGHT)
|
||||
{
|
||||
if (y + font->height >= ST7735_HEIGHT) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (*str == ' ')
|
||||
{
|
||||
if (*str == ' ') {
|
||||
// skip spaces in the beginning of the new line
|
||||
str++;
|
||||
continue;
|
||||
@@ -348,8 +407,8 @@ void ST7735_WriteString(uint16_t x, uint16_t y, const char *str, FontDef *font,
|
||||
* @param h 矩形的高度
|
||||
* @param color 用于填充矩形的颜色
|
||||
*/
|
||||
void ST7735_FillRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color)
|
||||
{
|
||||
void ST7735_FillRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h,
|
||||
uint16_t color) {
|
||||
// clipping
|
||||
if ((x >= ST7735_WIDTH) || (y >= ST7735_HEIGHT))
|
||||
return;
|
||||
@@ -368,13 +427,12 @@ void ST7735_FillRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16
|
||||
// Set the data/command pin to data mode
|
||||
HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_SET);
|
||||
// Send the color data for each pixel in the rectangle
|
||||
for (y = h; y > 0; y--)
|
||||
{
|
||||
for (x = w; x > 0; x--)
|
||||
{
|
||||
for (y = h; y > 0; y--) {
|
||||
for (x = w; x > 0; x--) {
|
||||
|
||||
// HAL_SPI_Transmit(&ST7735_SPI_PORT, data, sizeof(data), HAL_MAX_DELAY);
|
||||
HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, data, sizeof(data));
|
||||
// HAL_SPI_Transmit(&ST7735_SPI_PORT, data, sizeof(data),
|
||||
// HAL_MAX_DELAY);
|
||||
HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, data, sizeof(data));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,8 +442,8 @@ void ST7735_FillRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16
|
||||
|
||||
// Fill a rectangle area on the ST7735 LCD screen with a specified color
|
||||
// This function is optimized for speed, suitable for filling large areas
|
||||
void ST7735_FillRectangleFast(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color)
|
||||
{
|
||||
void ST7735_FillRectangleFast(uint16_t x, uint16_t y, uint16_t w, uint16_t h,
|
||||
uint16_t color) {
|
||||
// clipping
|
||||
if ((x >= ST7735_WIDTH) || (y >= ST7735_HEIGHT))
|
||||
return;
|
||||
@@ -417,10 +475,11 @@ void ST7735_FillRectangleFast(uint16_t x, uint16_t y, uint16_t w, uint16_t h, ui
|
||||
*
|
||||
* @param color 要填充的颜色值。
|
||||
*/
|
||||
void ST7735_FillScreen(uint16_t color)
|
||||
{
|
||||
// 使用FillRectangle函数填充整个屏幕,起始坐标为(0, 0),覆盖整个屏幕宽度和高度。
|
||||
ST7735_FillRectangle(0, 0, ST7735_WIDTH, ST7735_HEIGHT, color);
|
||||
void ST7735_FillScreen(uint16_t color) {
|
||||
// 使用FillRectangle函数填充整个屏幕,起始坐标为(0,
|
||||
// 0),覆盖整个屏幕宽度和高度。
|
||||
// ST7735_FillRectangle(0, 0, ST7735_WIDTH, ST7735_HEIGHT, color);
|
||||
ST7735_FillRectangleFast(0, 0, ST7735_WIDTH, ST7735_HEIGHT, color);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -429,12 +488,12 @@ void ST7735_FillScreen(uint16_t color)
|
||||
* 使用指定的颜色填充整个屏幕。该函数通过调用ST7735_FillRectangleFast函数,
|
||||
* 设置填充区域为整个屏幕的宽度和高度来实现快速填充屏幕的效果。
|
||||
*
|
||||
* @param color 填充屏幕所使用的颜色值。这是一个16位的颜色值,适用于ST7735显示屏。
|
||||
* @param color
|
||||
* 填充屏幕所使用的颜色值。这是一个16位的颜色值,适用于ST7735显示屏。
|
||||
*/
|
||||
void ST7735_FillScreenFast(uint16_t color)
|
||||
{
|
||||
// 调用快速填充矩形函数,参数为(起始X坐标, 起始Y坐标, 屏幕宽度, 屏幕高度, 颜色)
|
||||
// 用于填充整个屏幕为指定的颜色
|
||||
void ST7735_FillScreenFast(uint16_t color) {
|
||||
// 调用快速填充矩形函数,参数为(起始X坐标, 起始Y坐标, 屏幕宽度, 屏幕高度,
|
||||
// 颜色) 用于填充整个屏幕为指定的颜色
|
||||
ST7735_FillRectangleFast(0, 0, ST7735_WIDTH, ST7735_HEIGHT, color);
|
||||
}
|
||||
|
||||
@@ -451,8 +510,8 @@ void ST7735_FillScreenFast(uint16_t color)
|
||||
* @param h 图像的高度
|
||||
* @param data 图像数据指针,图像数据是16位的RGB565格式
|
||||
*/
|
||||
void ST7735_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *data)
|
||||
{
|
||||
void ST7735_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h,
|
||||
const uint16_t *data) {
|
||||
// 检查图像的起始坐标是否超出液晶屏的边界
|
||||
if ((x >= ST7735_WIDTH) || (y >= ST7735_HEIGHT))
|
||||
return;
|
||||
@@ -481,10 +540,10 @@ void ST7735_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint
|
||||
/**
|
||||
* ST7735_InvertColors函数用于控制ST7735显示屏的颜色反转功能。
|
||||
*
|
||||
* @param invert 一个布尔值,决定是否反转显示屏的颜色。如果invert为true,颜色将反转;如果为false,颜色将保持正常。
|
||||
* @param invert
|
||||
* 一个布尔值,决定是否反转显示屏的颜色。如果invert为true,颜色将反转;如果为false,颜色将保持正常。
|
||||
*/
|
||||
void ST7735_InvertColors(bool invert)
|
||||
{
|
||||
void ST7735_InvertColors(bool invert) {
|
||||
// 选择ST7735显示屏以进行通信。
|
||||
ST7735_Select();
|
||||
|
||||
@@ -503,8 +562,7 @@ void ST7735_InvertColors(bool invert)
|
||||
*
|
||||
* @param gamma 伽马曲线定义,指定要设置的伽马曲线
|
||||
*/
|
||||
void ST7735_SetGamma(GammaDef gamma)
|
||||
{
|
||||
void ST7735_SetGamma(GammaDef gamma) {
|
||||
// 选择ST7735显示器以进行通信
|
||||
ST7735_Select();
|
||||
|
||||
|
||||
@@ -274,6 +274,11 @@ extern "C"
|
||||
// 解释:在使用ST7735显示屏之前,必须调用此函数进行初始化设置
|
||||
void ST7735_Init(void);
|
||||
|
||||
// 启用/禁用DMA传输模式
|
||||
// 参数:enable - true启用DMA模式,false禁用DMA模式
|
||||
// 解释:初始化完成后调用此函数启用DMA模式可大幅提升刷屏速度
|
||||
void ST7735_EnableDMA(bool enable);
|
||||
|
||||
// 在指定位置绘制一个像素点
|
||||
// 参数:x - 像素点的X坐标;y - 像素点的Y坐标;color - 像素点的颜色
|
||||
// 解释:此函数用于在显示屏上的特定位置绘制一个单一颜色的像素点
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "cmsis_os.h"
|
||||
#include "cmsis_os2.h"
|
||||
#include "elog.h"
|
||||
#include "mp3_driver.h" // 添加MP3模块头文件
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -201,16 +202,19 @@ uint8_t WIFI_Connect_WiFi(const char *ssid, const char *password,
|
||||
elog_i(TAG, "等待WiFi基础连接响应...");
|
||||
if (!WIFI_WaitEvent("OK", "ERROR", 3000)) {
|
||||
elog_e(TAG, "WiFi基础连接失败");
|
||||
MP3_Play(WIFI_CONNECT_FAIL); // WiFi连接失败,播放提示音
|
||||
return 0;
|
||||
}
|
||||
|
||||
elog_i(TAG, "等待WiFi详细连接结果...");
|
||||
if (!WIFI_WaitEvent("+CWJAP:1", "+CWJAP:0", timeout_ms)) {
|
||||
elog_e(TAG, "WiFi详细连接失败");
|
||||
MP3_Play(WIFI_CONNECT_FAIL); // WiFi连接失败,播放提示音
|
||||
return 0;
|
||||
}
|
||||
|
||||
elog_i(TAG, "WiFi连接成功");
|
||||
MP3_Play(WIFI_CONNECT_OK); // WiFi连接成功,播放提示音
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
292
Core/Bsp/MultiButton-master/multi_button.c
Normal file
292
Core/Bsp/MultiButton-master/multi_button.c
Normal file
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Zibin Zheng <znbin@qq.com>
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "multi_button.h"
|
||||
|
||||
// Macro for callback execution with null check
|
||||
#define EVENT_CB(ev) do { if(handle->cb[ev]) handle->cb[ev](handle); } while(0)
|
||||
|
||||
// Button handle list head
|
||||
static Button* head_handle = NULL;
|
||||
|
||||
// Forward declarations
|
||||
static void button_handler(Button* handle);
|
||||
static inline uint8_t button_read_level(Button* handle);
|
||||
|
||||
/**
|
||||
* @brief Initialize the button struct handle
|
||||
* @param handle: the button handle struct
|
||||
* @param pin_level: read the HAL GPIO of the connected button level
|
||||
* @param active_level: pressed GPIO level
|
||||
* @param button_id: the button id
|
||||
* @retval None
|
||||
*/
|
||||
void button_init(Button* handle, uint8_t(*pin_level)(uint8_t), uint8_t active_level, uint8_t button_id)
|
||||
{
|
||||
if (!handle || !pin_level) return; // parameter validation
|
||||
|
||||
memset(handle, 0, sizeof(Button));
|
||||
handle->event = (uint8_t)BTN_NONE_PRESS;
|
||||
handle->hal_button_level = pin_level;
|
||||
handle->button_level = !active_level; // initialize to opposite of active level
|
||||
handle->active_level = active_level;
|
||||
handle->button_id = button_id;
|
||||
handle->state = BTN_STATE_IDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Attach the button event callback function
|
||||
* @param handle: the button handle struct
|
||||
* @param event: trigger event type
|
||||
* @param cb: callback function
|
||||
* @retval None
|
||||
*/
|
||||
void button_attach(Button* handle, ButtonEvent event, BtnCallback cb)
|
||||
{
|
||||
if (!handle || event >= BTN_EVENT_COUNT) return; // parameter validation
|
||||
handle->cb[event] = cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Detach the button event callback function
|
||||
* @param handle: the button handle struct
|
||||
* @param event: trigger event type
|
||||
* @retval None
|
||||
*/
|
||||
void button_detach(Button* handle, ButtonEvent event)
|
||||
{
|
||||
if (!handle || event >= BTN_EVENT_COUNT) return; // parameter validation
|
||||
handle->cb[event] = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the button event that happened
|
||||
* @param handle: the button handle struct
|
||||
* @retval button event
|
||||
*/
|
||||
ButtonEvent button_get_event(Button* handle)
|
||||
{
|
||||
if (!handle) return BTN_NONE_PRESS;
|
||||
return (ButtonEvent)(handle->event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the repeat count of button presses
|
||||
* @param handle: the button handle struct
|
||||
* @retval repeat count
|
||||
*/
|
||||
uint8_t button_get_repeat_count(Button* handle)
|
||||
{
|
||||
if (!handle) return 0;
|
||||
return handle->repeat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset button state to idle
|
||||
* @param handle: the button handle struct
|
||||
* @retval None
|
||||
*/
|
||||
void button_reset(Button* handle)
|
||||
{
|
||||
if (!handle) return;
|
||||
handle->state = BTN_STATE_IDLE;
|
||||
handle->ticks = 0;
|
||||
handle->repeat = 0;
|
||||
handle->event = (uint8_t)BTN_NONE_PRESS;
|
||||
handle->debounce_cnt = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if button is currently pressed
|
||||
* @param handle: the button handle struct
|
||||
* @retval 1: pressed, 0: not pressed, -1: error
|
||||
*/
|
||||
int button_is_pressed(Button* handle)
|
||||
{
|
||||
if (!handle) return -1;
|
||||
return (handle->button_level == handle->active_level) ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read button level with inline optimization
|
||||
* @param handle: the button handle struct
|
||||
* @retval button level
|
||||
*/
|
||||
static inline uint8_t button_read_level(Button* handle)
|
||||
{
|
||||
return handle->hal_button_level(handle->button_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Button driver core function, driver state machine
|
||||
* @param handle: the button handle struct
|
||||
* @retval None
|
||||
*/
|
||||
static void button_handler(Button* handle)
|
||||
{
|
||||
uint8_t read_gpio_level = button_read_level(handle);
|
||||
|
||||
// Increment ticks counter when not in idle state
|
||||
if (handle->state > BTN_STATE_IDLE) {
|
||||
handle->ticks++;
|
||||
}
|
||||
|
||||
/*------------Button debounce handling---------------*/
|
||||
if (read_gpio_level != handle->button_level) {
|
||||
// Continue reading same new level for debounce
|
||||
if (++(handle->debounce_cnt) >= DEBOUNCE_TICKS) {
|
||||
handle->button_level = read_gpio_level;
|
||||
handle->debounce_cnt = 0;
|
||||
}
|
||||
} else {
|
||||
// Level not changed, reset counter
|
||||
handle->debounce_cnt = 0;
|
||||
}
|
||||
|
||||
/*-----------------State machine-------------------*/
|
||||
switch (handle->state) {
|
||||
case BTN_STATE_IDLE:
|
||||
if (handle->button_level == handle->active_level) {
|
||||
// Button press detected
|
||||
handle->event = (uint8_t)BTN_PRESS_DOWN;
|
||||
EVENT_CB(BTN_PRESS_DOWN);
|
||||
handle->ticks = 0;
|
||||
handle->repeat = 1;
|
||||
handle->state = BTN_STATE_PRESS;
|
||||
} else {
|
||||
handle->event = (uint8_t)BTN_NONE_PRESS;
|
||||
}
|
||||
break;
|
||||
|
||||
case BTN_STATE_PRESS:
|
||||
if (handle->button_level != handle->active_level) {
|
||||
// Button released
|
||||
handle->event = (uint8_t)BTN_PRESS_UP;
|
||||
EVENT_CB(BTN_PRESS_UP);
|
||||
handle->ticks = 0;
|
||||
handle->state = BTN_STATE_RELEASE;
|
||||
} else if (handle->ticks > LONG_TICKS) {
|
||||
// Long press detected
|
||||
handle->event = (uint8_t)BTN_LONG_PRESS_START;
|
||||
EVENT_CB(BTN_LONG_PRESS_START);
|
||||
handle->state = BTN_STATE_LONG_HOLD;
|
||||
}
|
||||
break;
|
||||
|
||||
case BTN_STATE_RELEASE:
|
||||
if (handle->button_level == handle->active_level) {
|
||||
// Button pressed again
|
||||
handle->event = (uint8_t)BTN_PRESS_DOWN;
|
||||
EVENT_CB(BTN_PRESS_DOWN);
|
||||
if (handle->repeat < PRESS_REPEAT_MAX_NUM) {
|
||||
handle->repeat++;
|
||||
}
|
||||
EVENT_CB(BTN_PRESS_REPEAT);
|
||||
handle->ticks = 0;
|
||||
handle->state = BTN_STATE_REPEAT;
|
||||
} else if (handle->ticks > SHORT_TICKS) {
|
||||
// Timeout reached, determine click type
|
||||
if (handle->repeat == 1) {
|
||||
handle->event = (uint8_t)BTN_SINGLE_CLICK;
|
||||
EVENT_CB(BTN_SINGLE_CLICK);
|
||||
} else if (handle->repeat == 2) {
|
||||
handle->event = (uint8_t)BTN_DOUBLE_CLICK;
|
||||
EVENT_CB(BTN_DOUBLE_CLICK);
|
||||
}
|
||||
handle->state = BTN_STATE_IDLE;
|
||||
}
|
||||
break;
|
||||
|
||||
case BTN_STATE_REPEAT:
|
||||
if (handle->button_level != handle->active_level) {
|
||||
// Button released
|
||||
handle->event = (uint8_t)BTN_PRESS_UP;
|
||||
EVENT_CB(BTN_PRESS_UP);
|
||||
if (handle->ticks < SHORT_TICKS) {
|
||||
handle->ticks = 0;
|
||||
handle->state = BTN_STATE_RELEASE; // Continue waiting for more presses
|
||||
} else {
|
||||
handle->state = BTN_STATE_IDLE; // End of sequence
|
||||
}
|
||||
} else if (handle->ticks > SHORT_TICKS) {
|
||||
// Held down too long, treat as normal press
|
||||
handle->state = BTN_STATE_PRESS;
|
||||
}
|
||||
break;
|
||||
|
||||
case BTN_STATE_LONG_HOLD:
|
||||
if (handle->button_level == handle->active_level) {
|
||||
// Continue holding
|
||||
handle->event = (uint8_t)BTN_LONG_PRESS_HOLD;
|
||||
EVENT_CB(BTN_LONG_PRESS_HOLD);
|
||||
} else {
|
||||
// Released from long press
|
||||
handle->event = (uint8_t)BTN_PRESS_UP;
|
||||
EVENT_CB(BTN_PRESS_UP);
|
||||
handle->state = BTN_STATE_IDLE;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Invalid state, reset to idle
|
||||
handle->state = BTN_STATE_IDLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start the button work, add the handle into work list
|
||||
* @param handle: target handle struct
|
||||
* @retval 0: succeed, -1: already exist, -2: invalid parameter
|
||||
*/
|
||||
int button_start(Button* handle)
|
||||
{
|
||||
if (!handle) return -2; // invalid parameter
|
||||
|
||||
Button* target = head_handle;
|
||||
while (target) {
|
||||
if (target == handle) return -1; // already exist
|
||||
target = target->next;
|
||||
}
|
||||
|
||||
handle->next = head_handle;
|
||||
head_handle = handle;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stop the button work, remove the handle from work list
|
||||
* @param handle: target handle struct
|
||||
* @retval None
|
||||
*/
|
||||
void button_stop(Button* handle)
|
||||
{
|
||||
if (!handle) return; // parameter validation
|
||||
|
||||
Button** curr;
|
||||
for (curr = &head_handle; *curr; ) {
|
||||
Button* entry = *curr;
|
||||
if (entry == handle) {
|
||||
*curr = entry->next;
|
||||
entry->next = NULL; // clear next pointer
|
||||
return;
|
||||
} else {
|
||||
curr = &entry->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Background ticks, timer repeat invoking interval 5ms
|
||||
* @param None
|
||||
* @retval None
|
||||
*/
|
||||
void button_ticks(void)
|
||||
{
|
||||
Button* target;
|
||||
for (target = head_handle; target; target = target->next) {
|
||||
button_handler(target);
|
||||
}
|
||||
}
|
||||
84
Core/Bsp/MultiButton-master/multi_button.h
Normal file
84
Core/Bsp/MultiButton-master/multi_button.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Zibin Zheng <znbin@qq.com>
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef _MULTI_BUTTON_H_
|
||||
#define _MULTI_BUTTON_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
// Configuration constants - can be modified according to your needs
|
||||
#define TICKS_INTERVAL 5 // ms - timer interrupt interval
|
||||
#define DEBOUNCE_TICKS 3 // MAX 7 (0 ~ 7) - debounce filter depth
|
||||
#define SHORT_TICKS (300 / TICKS_INTERVAL) // short press threshold
|
||||
#define LONG_TICKS (1000 / TICKS_INTERVAL) // long press threshold
|
||||
#define PRESS_REPEAT_MAX_NUM 15 // maximum repeat counter value
|
||||
|
||||
// Forward declaration
|
||||
typedef struct _Button Button;
|
||||
|
||||
// Button callback function type
|
||||
typedef void (*BtnCallback)(Button* btn_handle);
|
||||
|
||||
// Button event types
|
||||
typedef enum {
|
||||
BTN_PRESS_DOWN = 0, // button pressed down
|
||||
BTN_PRESS_UP, // button released
|
||||
BTN_PRESS_REPEAT, // repeated press detected
|
||||
BTN_SINGLE_CLICK, // single click completed
|
||||
BTN_DOUBLE_CLICK, // double click completed
|
||||
BTN_LONG_PRESS_START, // long press started
|
||||
BTN_LONG_PRESS_HOLD, // long press holding
|
||||
BTN_EVENT_COUNT, // total number of events
|
||||
BTN_NONE_PRESS // no event
|
||||
} ButtonEvent;
|
||||
|
||||
// Button state machine states
|
||||
typedef enum {
|
||||
BTN_STATE_IDLE = 0, // idle state
|
||||
BTN_STATE_PRESS, // pressed state
|
||||
BTN_STATE_RELEASE, // released state waiting for timeout
|
||||
BTN_STATE_REPEAT, // repeat press state
|
||||
BTN_STATE_LONG_HOLD // long press hold state
|
||||
} ButtonState;
|
||||
|
||||
// Button structure
|
||||
struct _Button {
|
||||
uint16_t ticks; // tick counter
|
||||
uint8_t repeat : 4; // repeat counter (0-15)
|
||||
uint8_t event : 4; // current event (0-15)
|
||||
uint8_t state : 3; // state machine state (0-7)
|
||||
uint8_t debounce_cnt : 3; // debounce counter (0-7)
|
||||
uint8_t active_level : 1; // active GPIO level (0 or 1)
|
||||
uint8_t button_level : 1; // current button level
|
||||
uint8_t button_id; // button identifier
|
||||
uint8_t (*hal_button_level)(uint8_t button_id); // HAL function to read GPIO
|
||||
BtnCallback cb[BTN_EVENT_COUNT]; // callback function array
|
||||
Button* next; // next button in linked list
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Public API functions
|
||||
void button_init(Button* handle, uint8_t(*pin_level)(uint8_t), uint8_t active_level, uint8_t button_id);
|
||||
void button_attach(Button* handle, ButtonEvent event, BtnCallback cb);
|
||||
void button_detach(Button* handle, ButtonEvent event);
|
||||
ButtonEvent button_get_event(Button* handle);
|
||||
int button_start(Button* handle);
|
||||
void button_stop(Button* handle);
|
||||
void button_ticks(void);
|
||||
|
||||
// Utility functions
|
||||
uint8_t button_get_repeat_count(Button* handle);
|
||||
void button_reset(Button* handle);
|
||||
int button_is_pressed(Button* handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user