STM32蓝牙HID实战:打造低功耗、高性能的客制化键盘
一、项目概述
本项目旨在使用STM32单片机打造一款功能强大的蓝牙客制化键盘,它拥有以下特点:
- 九键布局,小巧便携: 满足日常使用需求,方便携带。
- 全键可编程: 所有按键和旋钮均可通过电脑软件自定义快捷键,实现个性化功能。
- 蓝牙无线连接: 摆脱线缆束缚,提供更自由的使用体验。
二、硬件设计
2.1 硬件平台
- 主控芯片: STM32F103C8T6
- 蓝牙模块: HC-05
- 按键: 机械轴*9
- 旋钮编码器: EC11
- 其他: 电焊、杜邦线、PCB板等
2.2 电路原理图
2.3 PCB设计
- 使用KiCad等EDA软件进行PCB设计,确保电路稳定可靠。
- 采用合理的布局,优化空间利用率,打造紧凑的外观。
三、软件设计
3.1 开发环境
- IDE: Keil MDK
- 编译器: ARMCC
- 调试器: ST-Link
3.2 软件架构
3.3 代码实现
3.3.1 蓝牙初始化
void Bluetooth_Init(void)
{// 设置蓝牙模块波特率为9600USART1_Init(9600);// 发送AT指令进入AT模式USART1_SendString("AT\r\n");// 设置蓝牙模块名称USART1_SendString("AT+NAME=CustomKeyboard\r\n");// 设置蓝牙模块配对密码USART1_SendString("AT+PIN=1234\r\n");// 设置蓝牙模块为从模式USART1_SendString("AT+ROLE=0\r\n");// 开启蓝牙模块USART1_SendString("AT+CMODE=1\r\n");
}
代码解释:
- 这部分代码首先初始化了STM32的USART1,用于与HC-05蓝牙模块通信。
- 随后,代码发送一系列AT指令配置蓝牙模块:
AT
: 测试指令,确保蓝牙模块连接正常。AT+NAME=CustomKeyboard
: 设置蓝牙模块名称为 "CustomKeyboard"。AT+PIN=1234
: 设置蓝牙模块配对密码为 "1234"。AT+ROLE=0
: 将蓝牙模块设置为从模式,等待连接。AT+CMODE=1
: 允许蓝牙模块连接任何地址的设备。
3.3.2 按键扫描
uint8_t KeyScan(void)
{// 扫描按键矩阵// ...// 返回按键值return key_value;
}
代码解释:
- 这段代码是按键扫描函数的框架。你需要根据你的硬件电路实现具体的按键扫描逻辑。
- 一般来说,你需要使用GPIO模拟矩阵键盘的扫描方式,检测哪个按键被按下。
- 函数最后需要返回被按下的按键码,如果没有按键按下则返回0。
3.3.3 旋钮读取
int8_t Encoder_Read(void)
{static uint8_t last_state = 0;uint8_t current_state = (GPIOB->IDR & 0x03); // 读取A、B相电平if (current_state != last_state) {if ((current_state == 0x01 && last_state == 0x03) ||(current_state == 0x03 && last_state == 0x02) ||(current_state == 0x02 && last_state == 0x00) ||(current_state == 0x00 && last_state == 0x01)) {return 1; // 顺时针旋转} else {return -1; // 逆时针旋转}}last_state = current_state;return 0; // 未旋转
}
代码解释:
- 这段代码实现了读取旋转编码器数值的逻辑。
- 它首先读取编码器的A、B两相的电平状态。
- 然后通过对比当前状态和上次状态,判断编码器的旋转方向。
- 如果顺时针旋转,返回1;逆时针旋转,返回-1;没有旋转,返回0。
3.3.4 数据处理
-
键盘使用特定的数据格式将按键信息和旋钮信息发送给电脑:
- 第一个字节代表数据类型:
0x01
:代表按键按下/弹起事件。0x02
:代表旋钮旋转事件。
- 第二个字节代表按键码或旋钮方向:
- 对于按键事件,该字节表示被按下或弹起的按键的键码。
- 对于旋钮事件,该字节为
0x00
表示逆时针旋转,0x01
表示顺时针旋转。
- 第一个字节代表数据类型:
-
定义按键码:
#define KEY_1 0x01
#define KEY_2 0x02
// ...
#define KEY_9 0x09
- 数据打包:
uint8_t data_buffer[2];void Data_Process(uint8_t key_value, int8_t encoder_value) {if (key_value != 0) {// 处理按键事件data_buffer[0] = 0x01; // 数据类型:按键data_buffer[1] = key_value; // 按键码} else if (encoder_value != 0) {// 处理旋钮事件data_buffer[0] = 0x02; // 数据类型:旋钮data_buffer[1] = (encoder_value > 0) ? 0x01 : 0x00; // 旋转方向}
}
3.3.5 蓝牙发送
void Bluetooth_Send(uint8_t *data, uint8_t len) {// 通过蓝牙串口发送数据for (uint8_t i = 0; i < len; i++) {USART1_SendByte(data[i]);}
}
代码解释:
- 这段代码实现了通过蓝牙串口发送数据的函数。
- 它接受一个指向数据缓冲区的指针
data
和数据的长度len
作为参数。 - 函数内部使用循环遍历数据缓冲区,并将每个字节数据通过
USART1_SendByte
函数发送出去。
代码实例:
// 假设 data_buffer 已经填充了要发送的数据
uint8_t data_buffer[2] = {0x01, 0x03}; // 例如:按键事件,按键码为 KEY_3// 通过蓝牙发送数据
Bluetooth_Send(data_buffer, sizeof(data_buffer));
完整代码示例:
// ... 其他代码 ...// 蓝牙发送函数
void Bluetooth_Send(uint8_t *data, uint8_t len) {// 通过蓝牙串口发送数据for (uint8_t i = 0; i < len; i++) {USART1_SendByte(data[i]);}
}// 主函数
int main(void) {// ... 初始化代码 ...while (1) {// 扫描按键uint8_t key_value = KeyScan();// 读取旋钮状态int8_t encoder_value = Encoder_Read();// 处理数据Data_Process(key_value, encoder_value);// 如果有数据需要发送if (data_buffer[0] != 0) {// 通过蓝牙发送数据Bluetooth_Send(data_buffer, sizeof(data_buffer));// 清空数据缓冲区data_buffer[0] = 0; }}
}
注意:
- 你需要根据你的硬件电路和数据协议,修改
KeyScan
,Encoder_Read
和Data_Process
函数的具体实现。 - 你需要将
USART1_SendByte
函数替换为你实际使用的串口发送函数。
四、电脑端软件
为了实现自定义快捷键功能,你需要开发一个电脑端软件,该软件需要实现以下功能:
- 连接蓝牙键盘: 搜索并连接你的蓝牙键盘设备。
- 接收数据: 持续接收来自蓝牙键盘的数据。
- 解析数据: 根据预定义的数据格式解析接收到的数据,识别按键事件和旋钮事件。
- 执行快捷键: 根据用户预先设置的快捷键映射关系,执行相应的操作。例如,用户可以将
KEY_1
映射为Ctrl+C
快捷键,将旋钮顺时针旋转映射为音量+
操作。
以下是一个使用 Python 实现的电脑端软件示例代码:
import bluetooth
import keyboard # 需要安装 keyboard 库: pip install keyboard# 蓝牙键盘设备地址
BT_ADDR = "00:11:22:33:44:55"
# 蓝牙服务UUID
BT_UUID = "00001124-0000-1000-8000-00805F9B34FB"def handle_data(data):"""处理接收到的数据"""data_type = data[0]data_value = data[1]if data_type == 0x01: # 按键事件key_code = data_valueprint(f"按键事件: {key_code}")# TODO: 根据 key_code 执行相应的快捷键操作elif data_type == 0x02: # 旋钮事件direction = "顺时针" if data_value == 0x01 else "逆时针"print(f"旋钮事件: {direction}")# TODO: 根据 direction 执行相应的操作def main():"""主函数"""print("正在搜索蓝牙设备...")devices = bluetooth.discover_devices(lookup_names=True)for addr, name in devices:if addr == BT_ADDR:print(f"找到设备: {name} ({addr})")breakelse:print("未找到设备")returnprint("正在连接...")sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)sock.connect((BT_ADDR, 1)) # 假设蓝牙服务端口号为 1print("连接成功")try:while True:data = sock.recv(1024)if data:handle_data(data)except KeyboardInterrupt:print("程序退出")finally:sock.close()if __name__ == "__main__":main()
代码说明:
- 导入库: 导入
bluetooth
库用于蓝牙通信,导入keyboard
库用于模拟键盘操作。 - 定义常量: 定义蓝牙键盘的设备地址
BT_ADDR
和服务 UUIDBT_UUID
。 handle_data()
函数: 该函数用于处理接收到的数据,根据数据类型和数据值执行相应的操作。main()
函数: 该函数是程序的入口点,负责搜索蓝牙设备、连接设备、接收数据并调用handle_data()
函数处理数据。- 模拟快捷键: 在
handle_data()
函数中,你可以使用keyboard
库提供的函数模拟键盘操作来实现快捷键功能。例如,使用keyboard.press_and_release('ctrl+c')
模拟Ctrl+C
快捷键。
注意:
- 你需要将
BT_ADDR
替换为你的蓝牙键盘的实际地址。 - 你需要根据你的键盘硬件和数据协议修改代码。
- 你需要根据你的需求修改
handle_data()
函数中的快捷键映射关系。
五、总结
本文介绍了如何使用STM32制作一款蓝牙客制化键盘,并详细讲解了硬件设计、软件设计以及数据传输协议等方面的内容。通过该项目,你可以学习到蓝牙通信、按键扫描、编码器读取等知识,并锻炼嵌入式系统开发能力。
你可以根据自己的需求,进一步扩展键盘的功能,例如增加RGB背光、支持多层配置、实现宏定义等。
相关文章:

STM32蓝牙HID实战:打造低功耗、高性能的客制化键盘
一、项目概述 本项目旨在使用STM32单片机打造一款功能强大的蓝牙客制化键盘,它拥有以下特点: 九键布局,小巧便携: 满足日常使用需求,方便携带。全键可编程: 所有按键和旋钮均可通过电脑软件自定义快捷键,实现个性化功…...
C++ STL容器:序列式容器-队queue,deque
摘要: CC STL(Standard Template Library,标准模板库)在C编程中的重要性不容忽视,STL提供了一系列容器、迭代器、算法和函数对象,这些组件极大地提高了C程序的开发效率和代码质量。 STL 容器 分为 2 大类 …...
简谈设计模式之单例模式
上一篇博客已经介绍了设计模式及其设计原则, 在这篇博客中笔者会介绍一下单例模式, 也是最简单的一种设计模式 单例模式 单例模式属于创建型模式. 它涉及到一个单一的类, 该类负责创建自己的对象, 同时确保只有单个对象被创建, 这个类提供了一种访问其唯一对象的方式, 可以直…...
在Spring Boot中实现多线程任务调度
在Spring Boot中实现多线程任务调度 大家好,我是微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿! 1. Spring Boot中的任务调度 Spring Boot通过集成Spring框架的Task Execution和Scheduling支持,提供…...
dify/api/models/account.py文件中的数据表
源码位置:dify\api\models\account.py accounts 表结构 字段英文名数据类型字段中文名字备注idStringUUIDIDnameString名称emailString邮箱passwordString密码password_saltString密码盐avatarString头像interface_languageString界面语言interface_themeString界…...
SQLAlchemy迁移数据库
SQLAlchemy迁移数据库 目录 SQLAlchemy迁移数据库安装Alembic配置Alembic编辑 alembic.ini编辑env.py生成迁移文件建表语句示例修改迁移文件命名格式 安装Alembic pip install alembic配置Alembic 执行初始化后会创建一个 alembic 目录,包含Alembic的配置文件 ale…...

Django文档简化版——Django快速入门——创建一个基本的投票应用程序
Django快速入门——创建一个基本的投票应用程序 准备工作1、创建虚拟环境2、安装django 1、请求和响应(1)创建项目(2)用于开发的简易服务器(3)创建投票应用(4)编写第一个视图1、编写…...

安全防御第三天(笔记持续更新)
1.接口类型以及作用 接口 --- 物理接口 三层口 --- 可以配置IP地址的接口 二层口 普通二层口 接口对 --- “透明网线” --- 可以将一个或者两个接口配置成为接口对,则 数据从一个接口进,将不需要查看MAC地址表,直接从另一个接口出;…...

【12321骚扰电话举报受理中心-短信验证安全分析报告】
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞…...
杂项——循迹模块调节方法
1-4 路灰度传感器的调节方法: 调节时探头应对着颜色较浅的上方(如果是黑白线赛道则应该将探头 对着白色上方调),轻轻的将全部可调电阻顺时针拧到底,再逆时针 慢慢回旋,直到对应探头的信号指示灯亮起后&…...

揭秘:源代码防泄密的终极秘籍
在当今信息科技高度发达的时代,源代码作为企业最核心的资产之一,其安全性不言而喻。源代码的泄露可能导致企业技术机密被竞争对手获取,进而威胁到企业的市场竞争力和长远发展。因此,源代码防泄密成为了企业信息安全工作的重中之重…...
avcodec_send_packet函数阻塞
用ffmpeg4.1.4开发一个播放器,解码过程如下,在每个函数前设置标志,测试发现程序阻塞在avcodec_send_packet函数。 while(true){av_read_frameavcodec_send_packetavcodec_receive_frameav_packet_unref } 解释如下: avcodec_se…...
一个parquet-go例子
一个parquet-go例子 使用go读写parquet,使用到了框架github.com/xitongsys/parquet-go 代码: package mainimport ("log""time""github.com/xitongsys/parquet-go-source/local""github.com/xitongsys/parquet-go/parquet&qu…...

扩散模型笔记
长参数“T”决定了生成全噪声图像所需的步长。在本文中,该参数被设置为1000,这可能显得很大。我们真的需要为数据集中的每个原始图像创建1000个噪声图像吗?马尔可夫链方面被证明有助于解决这个问题。由于我们只需要上一步的图像来预测下一步,…...

上海-LM科技(面经)
上海-LM科技 hr电话面 个人简介 个人信息的询问 是否知道芋道框架 技术面 算法题 14. 最长公共前缀(写出来即可) 聊一下Docker Docker核心概念总结Docker实战 聊一下AOP Spring AOP详解 聊一下JWT JWT 基础概念详解JWT 身份认证优缺点分析 Spring…...

用 Echarts 画折线图
https://andi.cn/page/621503.html...

C++的map / multimap容器
一、介绍 在C的map / multimap容器中,所有的元素均是pair类型(有关pair类型可以参考我之前写的 《C的set / multiset容器》的3.2中有介绍到)。 每对pair的第一个元素被称为关键字key,第二个元素被称为值value。因此,ma…...

双向链表 -- 详细理解和实现
欢迎光顾我的homepage 前言 双向链表是一种带头双向循环的链表。在双向链表中,首先存在着一个头结点;其次每个节点有指向下一个节点的指针next 和指向上一个节点的指针prev ;…...
WebGIS面试题
文章目录 1. 前端1.1. 选择器的优先级1.2. CSS 中它的布局有哪些?1.3. CSS3 的新特性1.4. CSS 的两种盒子模型1.5. CSS 的伪元素选择器和伪类选择器有哪些?1.6. ES6 的新特性1.7. 谈谈你对 promise 的理解1.8. 简单说一下原型链1.9. 简单说一下深浅拷贝1…...
代码随想录算法训练营:21/60
非科班学习算法day21 | LeetCode669:修剪二叉搜索树 ,Leetcode108:将有序数组转换为二叉搜索树 ,Leetcode538:把二叉搜索树转换为累加树 介绍 包含LC的两道题目,还有相应概念的补充。 相关图解和更多版本: 代码随想录 (progra…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...
Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?
Pod IP 的本质与特性 Pod IP 的定位 纯端点地址:Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址(如 10.244.1.2)无特殊名称:在 Kubernetes 中,它通常被称为 “Pod IP” 或 “容器 IP”生命周期:与 Pod …...