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:
2026-02-23 16:59:34 +08:00
parent ce8d6fd2eb
commit 9cadad138e
37 changed files with 980 additions and 201 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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. 所有语音播放函数已集成到相应按键处理逻辑中

View 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)

View 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)

View 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()

View File

@@ -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 // 版本号

View File

@@ -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();

View File

@@ -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 - 像素点的颜色
// 解释:此函数用于在显示屏上的特定位置绘制一个单一颜色的像素点