本帖最后由 lazy 于 2024-10-23 14:13 编辑
本帖最后由 lazy 于 2024-10-23 14:06 编辑
本帖最后由 lazy 于 2024-10-18 14:44 编辑
闲话
开始其实想做蓝牙键盘的,后来顺便把自拍杆功能也实现了。
虽然市面上有很多这样的产品,但是作为DIY爱好者的快乐不就是折腾吗。折腾使我快乐。
比如,刚到手的AiPi-KVM被我用12V点亮的故事
【我和小安派】故(shi)事(gu)AiPi-KVM短暂的一生后续
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=44995&fromuid=16612
后来买了一堆零件,还想着用烙铁焊上结果焊盘都干掉了。
HID[简]介(就是简单介绍)
The Human Interface Device (HID) ,即人机交互设备。定义了蓝牙在人机接口设备中的协议、特征和使用规程。典型的应用包括蓝牙鼠标、蓝牙键盘、蓝牙游戏手柄等。该协议改编自USB HID Protocol。
手机蓝牙的HID是指人机接口设备。
HID是蓝牙技术中的一种协议,用于描述设备与人之间的交互接口。下面是详细的解释:
- HID基本含义:HID是英文“Human Interface Devices”的缩写,中文可以翻译为“人机接口设备”。在蓝牙技术中,HID被广泛应用在各种设备之间,尤其是手机与外设之间。比如,我们常常用手机的蓝牙连接鼠标、键盘等外部设备,这时就会用到HID协议。
- 工作原理:当手机通过蓝牙与另一个设备建立连接时,如果另一设备支持HID协议,那么手机就可以识别并与之通信。这种通信允许用户通过这些外设设备进行更直观、便捷的操作。比如,使用蓝牙连接的键盘输入文字,或者使用鼠标移动屏幕上的光标。
- 手机中的应用场景:在日常生活中,手机蓝牙的HID功能经常被用于连接各种外部设备,如耳机、音箱、游戏手柄等。这使得手机的功能得到了扩展,提高了用户的使用体验。通过HID协议,这些设备可以与手机快速建立连接,并进行数据传输和控制。
总的来说,手机蓝牙的HID是指人机接口设备协议,它使得手机能够识别并与各种外部设备进行通信,提高了用户的使用体验和便捷性。
详细学习参考可以下资料
【USB系列】自定义USB HID设备(bzhou830)玛丽哥
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45000&fromuid=16612
【小安派试玩】基于HID协议的USB键盘测试(iiv)七哥
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=395&fromuid=16612
(二十)零基础开发小安派-Eyes-S1【番外篇】——BLE基础通讯(起个名字好难啊)莫工
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=44815&fromuid=16612
用btstack开发一个简单的蓝牙自拍杆
【低功耗蓝牙】⑤ HID协议
USB HID报告描述符教程 - 知乎
这里就不对HID协议进行详细介绍了(其实我也是一知半解),我们这里主题是如何实现自拍功能。
HID自拍原理
其实想要实现蓝牙自拍功能其实比较简单,目前市面上的手机大多都可以通过按“音量-”按键进行拍照。知道了这个实现起来就比较简单了。只要我们能够模拟点击“音量-”按键就可以实现遥控拍照功能。
既然知道了拍照原理下一步我们就要开始想办法通过HID实现这个功能。
前置条件
自拍杆HID报告描述【使用的话把# 替换换成 //】
# Report ID 1: Advanced buttons
0x05, 0x0C, # Usage Page (Consumer)
0x09, 0x01, # Usage (Consumer Control)
0xA1, 0x01, # Collection (Application)
0x85, 0x01, # Report Id (1)
0x15, 0x00, # Logical minimum (0)
0x25, 0x01, # Logical maximum (1)
0x75, 0x01, # Report Size (1)
0x95, 0x01, # Report Count (1)
0x09, 0xCD, # Usage (Play/Pause)
0x81, 0x06, # Input (Data,Value,Relative,Bit Field)
0x0A, 0x83, 0x01, # Usage (AL Consumer Control Configuration)
0x81, 0x06, # Input (Data,Value,Relative,Bit Field)
0x09, 0xB5, # Usage (Scan Next Track)
0x81, 0x06, # Input (Data,Value,Relative,Bit Field)
0x09, 0xB6, # Usage (Scan Previous Track)
0x81, 0x06, # Input (Data,Value,Relative,Bit Field)
0x09, 0xEA, # Usage (Volume Down)
0x81, 0x06, # Input (Data,Value,Relative,Bit Field)
0x09, 0xE9, # Usage (Volume Up)
0x81, 0x06, # Input (Data,Value,Relative,Bit Field)
0x0A, 0x25, 0x02, # Usage (AC Forward)
0x81, 0x06, # Input (Data,Value,Relative,Bit Field)
0x0A, 0x24, 0x02, # Usage (AC Back)
0x81, 0x06, # Input (Data,Value,Relative,Bit Field)
0xC0 # End Collection
作者:我是鹏老师 https://www.bilibili.com/read/cv15067064/ 出处:bilibili
还有另外一套
// 通用按键
0x05, 0x0C, // Usage Page (Consumer)
0x09, 0x01, // Usage (Consumer Control)
0xA1, 0x01, // Collection (Application)
0x85, 0x03, // Report ID (3)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x0B, // Report Count (11)
0x0A, 0x23, 0x02, // Usage (AC Home)
0x0A, 0x21, 0x02, // Usage (AC Search)
0x0A, 0xB1, 0x01, // Usage (AL Screen Saver)
0x09, 0xB8, // Usage (Eject)
0x09, 0xB6, // Usage (Scan Previous Track)
0x09, 0xCD, // Usage (Play/Pause)
0x09, 0xB5, // Usage (Scan Next Track)
0x09, 0xE2, // Usage (Mute)
0x09, 0xEA, // Usage (Volume Decrement)
0x09, 0xE9, // Usage (Volume Increment)
0x09, 0x30, // Usage (Power)
0x0A, 0xAE, 0x01, // Usage (AL Keyboard Layout)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, // Report Count (1)
0x75, 0x0D, // Report Size (13)
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
有了它我们就可以模拟手机按键了。
具体实现
这里蓝牙部分主要参考官方的教程里面的蓝牙功能
【完全开源】智能桌面助手——AiPi-DSL_Dashboard
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=42026&fromuid=16612
资料获取
AiPi-DSL_Dashboard资料包地址:https://docs.ai-thinker.com/dsl
AiPi-DSL_Dashboard资料包地址(Github): https://github.com/Ai-Thinker-Open/AiPi-Open-Kits/tree/master/AiPi-DSL_Dashboard
项目目录结构
-BLE_HID 负责蓝牙
-main 程序主入口
-wifi MQTT接入准备
程序
main
int main(void)
{
……
// 保留蓝牙相关任务
xTaskCreate(ble_hid_task, (char*)"ble_hid_task", 1024, NULL, 10, NULL);
vTaskStartScheduler();
……
}
ble_hid_dev.c 蓝牙任务管理
/**
* @brief HID 任务
*
* @param arg
*/
void ble_hid_task(void* arg)
{
// 主要是通知【lvgl】UI更新蓝牙状态的由于没有屏幕暂时注释以下两行代码
// ble_queue = xQueueCreate(1, 512);
// xTaskCreate(queue_receive_ble_task, "queue_ble_task", 1024, arg, 7, NULL);
vTaskDelay(200/portTICK_RATE_MS);
hid_key_num_t kb_num;
btblecontroller_em_config();
ble_init();
bas_init();
dis_init(0x01, 0x07AF, 0x707, 0x2A50);
hog_kb_init();
ble_kb_start();
ble_hid_queue = xQueueCreate(1, 4);
while (1) {
xQueueReceive(ble_hid_queue, &kb_num, portMAX_DELAY);
ble_hid_dev_send(kb_num);
}
}
ble_hid_dev.h文件中添加
typedef enum {
HID_KEY_NUMBLE_NONE = 0,
HID_KEY_NUMBLE_SELFIE_STICK,// 自拍杆
……
}
kb.h 文件中添加
typedef enum {
KEY_NUMBLE_SELFIE_STICK = 0X10, // 拍照
……
}
int send_selfie_stick_value(struct bt_conn* conn, uint8_t* keyboard_cmd); // 拍照指令
修改kb.c
增加
static uint8_t report_selfie_stick_map[] =
{
// Report ID 1: Advanced buttons
0x05, 0x0C, // Usage Page (Consumer)
0x09, 0x01, // Usage (Consumer Control)
0xA1, 0x01, // Collection (Application)
0x85, 0x01, // Report Id (1)
0x15, 0x00, // Logical minimum (0)
0x25, 0x01, // Logical maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x01, // Report Count (1)
0x09, 0xCD, // Usage (Play/Pause)
0x81, 0x06, // Input (Data,Value,Relative,Bit Field)
0x0A, 0x83, 0x01, // Usage (AL Consumer Control Configuration)
0x81, 0x06, // Input (Data,Value,Relative,Bit Field)
0x09, 0xB5, // Usage (Scan Next Track)
0x81, 0x06, // Input (Data,Value,Relative,Bit Field)
0x09, 0xB6, // Usage (Scan Previous Track)
0x81, 0x06, // Input (Data,Value,Relative,Bit Field)
0x09, 0xEA, // Usage (Volume Down)
0x81, 0x06, // Input (Data,Value,Relative,Bit Field)
0x09, 0xE9, // Usage (Volume Up)
0x81, 0x06, // Input (Data,Value,Relative,Bit Field)
0x0A, 0x25, 0x02, // Usage (AC Forward)
0x81, 0x06, // Input (Data,Value,Relative,Bit Field)
0x0A, 0x24, 0x02, // Usage (AC Back)
0x81, 0x06, // Input (Data,Value,Relative,Bit Field)
0xC0 // End Collection
//通用按键
// 0x05, 0x0C, // Usage Page (Consumer)
// 0x09, 0x01, // Usage (Consumer Control)
// 0xA1, 0x01, // Collection (Application)
// 0x85, 0x03, // Report ID (3)
// 0x15, 0x00, // Logical Minimum (0)
// 0x25, 0x01, // Logical Maximum (1)
// 0x75, 0x01, // Report Size (1)
// 0x95, 0x0B, // Report Count (11)
// 0x0A, 0x23, 0x02, // Usage (AC Home)
// 0x0A, 0x21, 0x02, // Usage (AC Search)
// 0x0A, 0xB1, 0x01, // Usage (AL Screen Saver)
// 0x09, 0xB8, // Usage (Eject)
// 0x09, 0xB6, // Usage (Scan Previous Track)
// 0x09, 0xCD, // Usage (Play/Pause)
// 0x09, 0xB5, // Usage (Scan Next Track)
// 0x09, 0xE2, // Usage (Mute)
// 0x09, 0xEA, // Usage (Volume Decrement)
// 0x09, 0xE9, // Usage (Volume Increment)
// 0x09, 0x30, // Usage (Power)
// 0x0A, 0xAE, 0x01, // Usage (AL Keyboard Layout)
// 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
// 0x95, 0x01, // Report Count (1)
// 0x75, 0x0D, // Report Size (13)
// 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
// 0xC0, // End Collection
};
经过测试以上两套报告描述都可以拍照,内容稍有不同。
修改
static ssize_t read_report_map(struct bt_conn* conn,
const struct bt_gatt_attr* attr, void* buf,
uint16_t len, uint16_t offset)
{
printf("read_report_map:%d \r\n", len);
// report_selfie_stick_map 这个是自拍杆报告描述
return bt_gatt_attr_read(conn, attr, buf, len, offset, report_selfie_stick_map,
sizeof(report_selfie_stick_map));
}
增加
int send_selfie_stick_value(struct bt_conn* conn, uint8_t* keyboard_cmd)
{
struct bt_gatt_attr* attr;
attr = &attrs[BT_CHAR_BLE_HID_REPORT_ATTR_VAL_INDEX];
return bt_gatt_notify(conn, attr, keyboard_cmd, 1);
}
ble_hid_dev_send
方法中添加
switch (key_num)
{
case HID_KEY_NUMBLE_SELFIE_STICK:
{
key_vaule[0] = KEY_NUMBLE_SELFIE_STICK; // 0x10
// 按下音量键-
send_selfie_stick_value(ble_conn_handle, key_vaule);
vTaskDelay(100/portTICK_RATE_MS);
key_vaule[0] = 0x00;
// 释放音量键-
send_selfie_stick_value(ble_conn_handle, key_vaule);
LOG_I("HID SEND: 0x10");
}
break;
……
}
拍照发送的指令为什么是0x10呢,看下面消息体信息就会理解。
0x10表示音量-
现在消息发送搞定了,那要怎么将消息发出去,如何触发呢。
按钮
目前最简单的就是增加按钮了那么如何增加按钮呢
可以参考,以下两张图摘自
32单片机基础:GPIO输入
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_64484421/article/details/136200996
————————————————
原文链接:https://blog.csdn.net/weixin_64484421/article/details/136200996
两种方式,我们一般用下接的方式。
第一个图:注意点。当按键按下,PA0接地,被置为低电平, 但是一旦按键松手,PA0悬空,引脚电压不确定。所以无论怎么读引脚也不知道知否被按下,所以为了解决这个问题,所以必须要求PA0是上拉输入的模式,这样引脚悬空的话,就会被置为高电平,这样我们我们就可以读取PA0的电压就知道按键是否被按下。
但是第二个图就不会出现问题,按下时,被置为低电平,松手,由于上拉电阻的作用,被置为高电平。这样引脚就不会出现浮空状态。所以此时PA0可以配置浮空输入和上拉输入。上拉输入,两个电阻共同作用,这样高电平就会更加稳定一些,
第三个图同样注意要使用下拉输入模式。
这里没有上下拉,直接使用的Ai-M61-32SU内部的上拉
struct bflb_device_s* btn_gpio; // 初始化gpio
int btn_clicked = 0;
// 按钮检测任务
static void btn_event(void* args){
while (1)
{
int status = bflb_gpio_read(btn_gpio, GPIO_PIN_14);
// 检测gpio14是否为低电平,默认上拉高电平
if(status == 0){
// 消除抖动
vTaskDelay(15/portTICK_RATE_MS);
再判断一次
if(status == 0){
// 防止多次触发
if(btn_clicked){
continue;;
}
LOG_I("点击");
btn_clicked = 1;
hid_key_num_t hid_key_num = HID_KEY_NUMBLE_SELFIE_STICK;
// 发送音量-按键进行拍照
xQueueSend(ble_hid_queue, &hid_key_num, portMAX_DELAY);
}
}else{
btn_clicked = 0;
}
}
}
int main(void)
{
board_init();
// gpio初始化
btn_gpio = bflb_device_get_by_name("gpio");
// 默认上拉
bflb_gpio_init(btn_gpio, GPIO_PIN_14, GPIO_INPUT| GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_1);
……
// 创建按钮检测任务
xTaskCreate(btn_event, "btn_event", 1024, NULL, 1, NULL);
……
vTaskStartScheduler();
}
以上就完成了自拍杆的全部功能了。
这个就是发现的自拍杆设备蓝牙名称与外观。
其他
这里有个有意思的地方就是可以改变蓝牙的外观图标。HID服务的UUID是0x1812,鼠键的外观是0x03C0,键盘的外观是0x03C1,鼠标的外观是0x03C2,游戏手柄的外观是0x03C3。
想要改变蓝牙设备外观
修改 kb.h 第10行
#define BLE_APPEARANCE_HID_KEYBOARD 0x03C3
编译并烧录完成后,搜索蓝牙就可以看到效果了。
源码在评论区自取。
目前只实现了功能,外观上还没设计比较丑陋暂时就不上图了。
外壳设计
【外壳】蓝牙自拍杆3D外壳设计
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45390&fromuid=16612
上一期说到蓝牙自拍杆,要求必须有外壳。
目前想设计的不只是蓝牙自拍杆功能,想加点其他功能。
对3D建模不熟悉的我,很是为难:'( 然后就是漫长的学习时间。
之前用的是Ai-M61-32SU 后来觉得板子有点大,并且USB口不能给电池充电。
这里换成了AiPi-CAM-D200 小巧一些,摄像头暂时去掉。
为了方便做壳,先做了一个屏幕的的3D模型
正视图
侧视图
然后是AiPi-CAM-D200 模型图
这里从嘉立创直接导出来转换过来的。
有了他们外壳指日可待:D、可待感觉外壳有些难度呢。
然后是部分外壳
这个后盖我想做成推拉的,然后呢,不会做。
以下是 1.3寸屏幕和AiPi-CAM-D200模型图。
最终效果展示
【补充】手机蓝牙自拍杆
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45408&fromuid=16612
(出处: 物联网开发者社区-安信可论坛)
经过不懈努力,自拍杆模块还是不够完美,感觉如果要做小的话还是得自己画板子集成。主要是线材太占地方了,大部分空间都被线给占用了。去掉排针和线材,自己画板子没点专业知识好像也不太行有很大难度。👀️ 目前只做到了当前效果。想试着改变外壳形状和结构但现在还没有什么好的思路。😄
DIY 主题帖
【DIY 电子作品】Ai-M61-32SU 手机蓝牙自拍杆
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45370&fromuid=16612
(出处: 物联网开发者社区-安信可论坛)
目前主要使用的硬件
- 1.3 寸 TFT 模块液晶显示屏 LCD 分辨率 240*240 IPS 模块 7 针 驱动 ST7789
- 小安派-Cam-D200
- 十字圆头带垫自攻螺丝 M1.4*3*4
- 2.54 单排母 直针 1*8P
- 杜邦线排线
- 充放电模块
- 按钮两颗
- 502 胶水
- 牙签两根 😄 【后面图片可以看到作用】
感觉做出了老式照相机的感觉
一按一个冒烟的,边上带个启动快门那种。
本来想着把电池做进去,后来发现内部控件被线占满就很难受。如果把电池放进去就要做大不美观。
按钮
按钮细节
两个按钮共地,然后另一端分别接到 GPIO17 与 GPIO18 上面,控制两个按键,目前只生效一个还没想好后续功能做些什么。
这里主要就是实现自拍功能。
充放电模块(很便宜)
还有电池
用的是 200 毫安的,这个是两个 200 毫安电池并联起来的价格很便宜啊
3 分钱一块
还有就是这个感觉很神奇,这个是个杯子里面测温的东西,哪个小海绵块触碰就显示温度。这种按钮怎么实现的。隔着塑料也可以用。
好像胶电容式触摸按钮吗,看着 ESP32 有 touch pin,不知道 M61 系列开发板有没有这个针脚。
关联帖子
【已解决】熬了两晚终于点亮了 1.3 寸 7789V 的屏幕
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45377&fromuid=16612
(出处: 物联网开发者社区-安信可论坛)
【外壳】蓝牙自拍杆 3D 外壳设计
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45390&fromuid=16612
(出处: 物联网开发者社区-安信可论坛)
演示视频
【【DIY 电子作品】Ai-M61-32SU 手机蓝牙自拍杆】
<iframe class="iframe__video" src="https://player.bilibili.com/player.html?bvid=BV1eiyZY6Ejr"></iframe>
问题汇总
🎉️ 问题】关于LVGL使用问题(已解决)
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45375&fromuid=16612
(出处: 物联网开发者社区-安信可论坛)
【已解决】熬了两晚终于点亮了1.3寸7789V的屏幕
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45377&fromuid=16612
(出处: 物联网开发者社区-安信可论坛)
【外壳】蓝牙自拍杆3D外壳设计
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45390&fromuid=16612
(出处: 物联网开发者社区-安信可论坛)
【补充】手机蓝牙自拍杆
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45408&fromuid=16612
(出处: 物联网开发者社区-安信可论坛)
能力有限,努力学习ing。以此抛砖引玉,期待各位大佬的作品。🎉️ 🎉️🎉️