项目介绍
这里是我参加Funpack第三季第一期活动的任务总结报告,我所完成的是任务二,使用蓝牙连接功能,通过板子上的两个按钮,分别进行短按长按实现模拟滚轮上下翻页一行和发送特定字符。
项目的大概思路如下:
- 准备工作:
- 使用官方的IDE,确保已经下载了官方的SDK,并理解了蓝牙HID设备的基本工作原理。
- 创建蓝牙HID例程:
- 创建官方的蓝牙HID例程并下载到目标设备上。该例程应该包含了基本的键盘功能。
- 修改GATT配置表:
- 对GATT配置表进行修改,将其从键盘配置修改为键盘鼠标的复合设备配置,以支持鼠标功能。这需要修改服务和特性等相关设置。
- 创建另一个按键实例:
- 在项目配置中创建另一个按键实例,以便扩展按键的功能。这将涉及硬件接线和配置。
- 修改代码:
- 在代码中对按键的中断事件处理进行修改,包括添加按钮状态的更新逻辑。
- 在主任务循环中不断监测按钮状态的变化,一旦变化,设置蓝牙事件信号,以通知系统处理对应的事件。
- 处理蓝牙事件信号:
- 修改蓝牙信号处理过程,使其可以发送滚轮滑动等鼠标功能或特定字符串给PC端。这可能涉及蓝牙通信协议和数据格式的处理。
- 测试与调试:
- 在修改完成后,进行系统测试以确保新功能正常工作。通过调试工具和日志记录来验证蓝牙通信的准确性和稳定性。
- 优化与扩展:
- 根据需要,进一步优化代码,增加其他功能和改进用户体验。
- 考虑如何处理各种传感器数据或额外的输入信息,以扩展设备的功能性。
以上思路提供了在基于官方的蓝牙HID例程的基础上实现键盘鼠标复合设备功能的详细步骤,涉及硬件设置、固件修改和蓝牙通信功能的调整。
主要硬件介绍
XG24-EK2703A是基于EFR32MG24芯片的开发套件,具有超低成本、低功耗和便携特点。支持2.4GHz无线通信,兼容蓝牙LE、mesh、Zigbee、Thread和Matter协议,适合物联网产品开发。
套件集成了所有例程和板卡资料在SDK开发包中,结合Simplicity Studio Version 5工具简化操作。特性包括USB接口、SEGGER J-Link调试器、LED/按钮、虚拟COM端口、数据包跟踪接口、mikroBus插座、Qwiic连接器,以及32位ARM Cortex-M33,78MHz主频,1536kB闪存和256kB RAM。
主要软件介绍
这款开发板具有多样化的开发途径,我们可以根据个人偏好选择使用官方Simplicity Studio IDE或者采用MicroPython方式进行开发。官方提供的丰富SDK中包含各种示例程序,这些示例程序不仅帮助您了解开发板的功能和潜力,还提供了实用的参考和指导。
在着手编写代码之前,首要任务是对蓝牙的GATT表进行精心配置。这项工作至关重要,因为它定义了设备间通信的方式以及数据交换的规范。通过适当配置GATT表,您可以确保设备在通信过程中遵循正确的协议,确保数据的准确传输和互操作性。
在配置GATT表时,我们需要:
确定设备所需的功能和通信需求,如键盘、鼠标、或传感器等。
深入研究SDK提供的示例程序,理解如何配置GATT表、定义服务和特征。
对SDK中提供的示例程序进行借鉴或修改,根据设备的具体需求进行适当的调整。
定义适当的服务、特征和描述符,以确保设备之间的通信和数据交换是完整而准确的。
GATT表的正确配置是设备通信和功能实现的基础。它为您的开发过程打下坚实基础,帮助您确保设备可以准确、高效地与其他设备进行通信,并顺利实现所需的功能。这一步骤的重要性不可言喻,是整个开发过程中不可或缺的关键环节。
修改hid的报告描述符
由于原例程的报告描述符是只有包含键盘的,所以我们对其进行修改并添加上鼠标的报告数据包格式。我们可以去usb中文网那里有许多的鼠标报告描述符事例可供参考
我们把它加到项目的guest配置表那边
接下来我们就可以对项目中具体的业务进行描述
按钮状态变化的处理
enum{
idle_s,
wheel_up_s,
wheel_down_s,
str_s
};
typedef struct{
uint8_t btn1_s;
uint8_t btn2_s;
uint8_t send_mesage;
}Button_Status;
Button_Status button_sta={0,0,idle_s};
sl_sleeptimer_timer_handle_t timer_2s;
上面就是设置了一些状态量和定义了一个结构体专门用来记录两个按钮的变化即当前需要对蓝牙发送什么,同时还定义了一个定时器结构体
void sl_button_on_change(const sl_button_t *handle)
{
bool running;
if (&sl_button_btn0 == handle) {
if (sl_button_get_state(handle) == SL_SIMPLE_BUTTON_PRESSED) {
button_sta.btn1_s=1;
if(button_sta.btn2_s == 1){
sl_sleeptimer_start_timer_ms(&timer_2s,2000,timer_2s_callback,NULL,0,SL_SLEEPTIMER_NO_HIGH_PRECISION_HF_CLOCKS_REQUIRED_FLAG);
}
app_log("Button1 pushed - callback\r\n");
} else {
button_sta.btn1_s=0;
sl_sleeptimer_stop_timer(&timer_2s);
if(button_sta.send_mesage != str_s){
button_sta.send_mesage =wheel_up_s;
sl_bt_external_signal(1);
}else{
if(button_sta.btn2_s !=1)
button_sta.send_mesage =idle_s;
}
app_log("Button1 released - callback \r\n");
}
}
if (&sl_button_btn1 == handle) {
if (sl_button_get_state(handle) == SL_SIMPLE_BUTTON_PRESSED) {
button_sta.btn2_s=1;
if(button_sta.btn1_s == 1){
sl_sleeptimer_start_timer_ms(&timer_2s,2000,timer_2s_callback,NULL,0,SL_SLEEPTIMER_NO_HIGH_PRECISION_HF_CLOCKS_REQUIRED_FLAG);
}
app_log("Button2 pushed - callback\r\n");
} else {
button_sta.btn2_s=0;
sl_sleeptimer_stop_timer(&timer_2s);
if(button_sta.send_mesage != str_s){//这两种状态在蓝牙时间中恢复为idle
button_sta.send_mesage =wheel_down_s;
sl_bt_external_signal(1);
}else{
if(button_sta.btn1_s !=1)
button_sta.send_mesage =idle_s;
}
app_log("Button2 released - callback \r\n");
}
}
// sl_bt_external_signal(1);//这个是不是关键
}
这里的话当一个按钮被按下时会进行判断,另一个按钮是否也处于按下状态,若是的话则会开启两秒定时器等待它超时触发。而当一个按钮松开时只会判断当前是否是发送特定字符串状态,如果不是的话则判定为滚轮滑动时间并进行相应的标志位处理,这里的滚动行为分为看滚动和往下滚动。
其中,sl_bt_external_signal(1);这个函数是设置了蓝牙处理事件的信号,执行了这个函数系统会进行蓝牙事件处理我们就在这个事件处理中进行一些拉压数据的发送
蓝牙事件处理
void sl_bt_on_event(sl_bt_msg_t *evt)
{
sl_status_t sc;
.....
case sl_bt_evt_system_external_signal_id:
if (notification_enabled == 1) {
if(button_sta.send_mesage == str_s){
sc=send_eetree();
}else if(button_sta.send_mesage == wheel_up_s){
app_log("wheel_up_s111\r\n");
memset(mouse_report_data, 0, sizeof(mouse_report_data));
mouse_report_data[REPORT_ID_INDEX] = MOUSE_REPORT_ID;
mouse_report_data[WHEEL_INDEX] = 0x01;
sc = sl_bt_gatt_server_notify_all(gattdb_report,
sizeof(mouse_report_data),
mouse_report_data);
button_sta.send_mesage=idle_s;
}else if(button_sta.send_mesage == wheel_down_s){
app_log("wheel_down_s111\r\n");
memset(mouse_report_data, 0, sizeof(mouse_report_data));
mouse_report_data[REPORT_ID_INDEX] = MOUSE_REPORT_ID;
mouse_report_data[WHEEL_INDEX] = 0xff;
sc = sl_bt_gatt_server_notify_all(gattdb_report,
sizeof(mouse_report_data),
mouse_report_data);
button_sta.send_mesage=idle_s;
}
app_assert_status(sc);
}
break;
.....
// -------------------------------
// Default event handler.
default:
break;
}
}
这个蓝牙事件处理中我们可以看到对于当前需要发送的消息进行判断,第1个是发送特定的字符串第二第3个则是进行滚动的发送。
sl_status_t send_eetree(){
sl_status_t sc;
uint8_t temp_str[]={0x08,0x08,0x17,0x15,0x08,0x08,0x37,0x06,0x11};
app_log("22 %d\n",sizeof(temp_str));
for(uint8_t i=0;i<sizeof(temp_str);i++){
memset(keyboard_report_data, 0, sizeof(keyboard_report_data));
keyboard_report_data[REPORT_ID_INDEX] = KEYBOARD_REPORT_ID;
if(temp_str[i]==0x37){
keyboard_report_data[MODIFIER_INDEX] = CAPSLOCK_KEY_OFF;
}else{
keyboard_report_data[MODIFIER_INDEX] = CAPSLOCK_KEY_ON;
}
app_log("33 0x%x\n",temp_str[i]);
keyboard_report_data[DATA_INDEX] = temp_str[i];
sc = sl_bt_gatt_server_notify_all(gattdb_report,
sizeof(keyboard_report_data),
keyboard_report_data);
app_assert_status(sc);
memset(keyboard_report_data, 0, sizeof(keyboard_report_data));
keyboard_report_data[REPORT_ID_INDEX] = KEYBOARD_REPORT_ID;
sc = sl_bt_gatt_server_notify_all(gattdb_report,
sizeof(keyboard_report_data),
keyboard_report_data);
app_assert_status(sc);
sl_sleeptimer_delay_millisecond(20);
}
return sc;
}
该函数就是发送特定的字符串,通过for循环来一次遍历字符串数组里面的每一个字符并发送。
总结
在本次活动中,学习了使用Silicon Labs官方的IDE,及蓝牙的使用,GATT表的配置。在过程中遇到的问题,通过百度搜索都能找到适合的答案,使自我得到了提升感谢硬禾学堂平台。