结果帖
论坛里由很多大佬们都添加成功的添加HID,本文就不赘述了,就节选个人认为重要的点给大家分享一下我的做法和过程
1、Cube MX HID配置
必须勾选以上两个,不然会缺少相关的代码文件。
以上需要配置为USB
电流设置为0ma,相关的内存分配都是16k 8k,但是实际的代码还是会编译不过,后续会详细提及。
Tread X配置相对简单,只要打开应用后,勾选上图的初始化 并 选择创建任务即可。
完成以上步骤之后直接生成代码即可,注:时钟是配置为250MHz的主频
2、代码调试
生成后的代码时可以以直接编译通过的,但是直接烧录之后,调试模式会发现代码连while 1都进不去,就卡死再某个地方了,而且没有触发hard fault,可以说是非常绝望啊,必须要一行一行的仔细查看代码了,最简单的调试方法就是静茹仿真模式,不断的打断点,开代码,理解代码。
再经过不断的调试后发现,代码时运行到初始化创建USBx相关的代码后卡死的,以下时发现流程:
在main.C可以看见多了一个 “MX_ThreadX_Init”的函数,跳转过去文件是app_threadx.c,可以看见有一个创建任务的初始化函数(下文),所以可以猜测相对应的 USB X也会由一个,并且他们应该在同一个地方被调用。
以下创建名为 tx_app_thread 的任务的代码。
UINT App_ThreadX_Init(VOID *memory_ptr) { UINT ret = TX_SUCCESS; TX_BYTE_POOL *byte_pool = (TX_BYTE_POOL*)memory_ptr; /* USER CODE BEGIN App_ThreadX_MEM_POOL */ /* USER CODE END App_ThreadX_MEM_POOL */ CHAR *pointer; /* Allocate the stack for tx app thread */ if (tx_byte_allocate(byte_pool, (VOID**) &pointer, TX_APP_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS) { return TX_POOL_ERROR; } /* Create tx app thread. */ if (tx_thread_create(&tx_app_thread, "tx app thread", tx_app_thread_entry, 0, pointer, TX_APP_STACK_SIZE, TX_APP_THREAD_PRIO, TX_APP_THREAD_PREEMPTION_THRESHOLD, TX_APP_THREAD_TIME_SLICE, TX_APP_THREAD_AUTO_START) != TX_SUCCESS) { return TX_THREAD_ERROR; } /* USER CODE BEGIN App_ThreadX_Init */ /* USER CODE END App_ThreadX_Init */ return ret; }
于是全文搜索 App_ThreadX_Init,可以发现只有一处调用,其他都是注释,跳转后能看见该函数,调用了App_ThreadX_Init 与 MX_USBX_Device_Init。
并且tx_application_define是由最开始的MX_ThreadX_Init函数调用的,所以这就是我们想找的地方。
VOID tx_application_define(VOID *first_unused_memory) { /* USER CODE BEGIN tx_application_define_1*/ /* USER CODE END tx_application_define_1 */ #if (USE_STATIC_ALLOCATION == 1) UINT status = TX_SUCCESS; VOID *memory_ptr; if (tx_byte_pool_create(&tx_app_byte_pool, "Tx App memory pool", tx_byte_pool_buffer, TX_APP_MEM_POOL_SIZE) != TX_SUCCESS) { /* USER CODE BEGIN TX_Byte_Pool_Error */ /* USER CODE END TX_Byte_Pool_Error */ } else { /* USER CODE BEGIN TX_Byte_Pool_Success */ /* USER CODE END TX_Byte_Pool_Success */ memory_ptr = (VOID *)&tx_app_byte_pool; status = App_ThreadX_Init(memory_ptr); if (status != TX_SUCCESS) { /* USER CODE BEGIN App_ThreadX_Init_Error */ while(1) { } /* USER CODE END App_ThreadX_Init_Error */ } /* USER CODE BEGIN App_ThreadX_Init_Success */ /* USER CODE END App_ThreadX_Init_Success */ } if (status != TX_SUCCESS) { /* USER CODE BEGIN App_ThreadX_Init_Error */ while(1) { } /* USER CODE END App_ThreadX_Init_Error */ } /* USER CODE BEGIN App_ThreadX_Init_Success */ /* USER CODE END App_ThreadX_Init_Success */ } if (tx_byte_pool_create(&ux_device_app_byte_pool, "Ux App memory pool", ux_device_byte_pool_buffer, UX_DEVICE_APP_MEM_POOL_SIZE) != TX_SUCCESS) { /* USER CODE BEGIN UX_Device_Byte_Pool_Error */ /* USER CODE END UX_Device_Byte_Pool_Error */ } else { /* USER CODE BEGIN UX_Device_Byte_Pool_Success */ /* USER CODE END UX_Device_Byte_Pool_Success */ memory_ptr = (VOID *)&ux_device_app_byte_pool; status = MX_USBX_Device_Init(memory_ptr); if (status != UX_SUCCESS) { /* USER CODE BEGIN MX_USBX_Device_Init_Error */ while(1) { } /* USER CODE END MX_USBX_Device_Init_Error */ } /* USER CODE BEGIN MX_USBX_Device_Init_Success */ /* USER CODE END MX_USBX_Device_Init_Success */ }
而且能看见
memory_ptr = (VOID *)&tx_app_byte_pool; memory_ptr = (VOID *)&ux_device_app_byte_pool;
这两行像是申请空间大小的赋值。所以直接开始实验:
直接看现象:在代码在烧录后连While 1 里的灯都不会翻转了(后面发现了使用Tread X后不在main的While 1里面循环了,但是当时不清楚)。
调试模式:打断点可以发现代码是可以运行到
memory_ptr = (VOID *)&ux_device_app_byte_pool;
这一行之后就卡死了,所以直接屏蔽后,代码是可以跑起来的,判断依据是断点可以进入到,我们创建的任务里面了,即tx_app_thread_entry里。
所以将USB X的空间给大就可以了,几重跳转之后可以发现,UX_DEVICE_CLASS_HID_MAX_EVENTS_QUEUE 着宏是8,改为16之后代码就不会卡死了。
紧接着在app_ux_device_thread_entry这个函数中添加初始化的代码,就可以正确的被电脑识别为HID设备了。
并且发现这个函数有且只会在 MX_USBX_Device_Init 中调用,所以必须在复位 或 开发板上电前,先将USB口接到电脑后再上电,不然不会识别设备。
3、实验
再修改一部分代码确定是插入了我的设备,所以我小改了一下制造商的名字,如下win 10上可以完美的显示我们的修改!
以上就完成了HID键盘的初始化。下面会结合手势传感器,实现手势翻页笔的功能。
3.1、键值发送
在上面得知创建的任务会不断的进入tx_app_thread_entry这个函数中,于是有了以下代码:
void tx_app_thread_entry(ULONG thread_input) { /* USER CODE BEGIN tx_app_thread_entry */ uint16_t paj7620u2_state=0; uint8_t win_state=0; uint8_t key=0; while(1) { tx_thread_sleep(300); HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); get_paj7620u2_state(); HID_keyboard_send(0x04); } /* USER CODE END tx_app_thread_entry */ }
以上的代码实现每间隔300ms翻转一次IO电平闪灯,获取一次手势,并且发送“a”给电脑,0x04就是“a”。
键盘发送函数封装
void HID_keyboard_send(uint8_t win_en){ send_keyboard_win_data(&hid_event,win_en); ux_device_class_hid_event_set(hid_keyboard,&hid_event); clear_keyboard_data(&hid_event); ux_device_class_hid_event_set(hid_keyboard,&hid_event); } void send_keyboard_win_data(UX_SLAVE_CLASS_HID_EVENT *param,uint8_t key_value){ memset(keyboard_value,0,sizeof(keyboard_value)); param->ux_device_class_hid_event_length = 8; keyboard_value[2] = key_value; memcpy((uint8_t*)¶m->ux_device_class_hid_event_buffer[0],(uint8_t*)&keyboard_value[0],sizeof(keyboard_value)); }
以上的代码实现“脑残化”填键值就可以发。
后面要实现翻页笔就需要将“a”替换为左、右箭头,所以要确认对应的键值。
由上图可以确认,上下左右按键对应的键值了,所以对应手势发送键值即可实现翻页笔了!
所以进一步的将应用代码改为,获取传感器的返回值后直接,发送出去
void tx_app_thread_entry(ULONG thread_input) { /* USER CODE BEGIN tx_app_thread_entry */ uint16_t paj7620u2_state=0; while(1) { tx_thread_sleep(300); HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); paj7620u2_state=get_paj7620u2_state(); HID_keyboard_send(paj7620u2_state); } /* USER CODE END tx_app_thread_entry */ }
原本的传感器驱动肯定不会返回键值的,所以还要修改传感器的返回值。(PS:目前先不考虑应用分层,先跑着,所有先“恶心”一下下)
uint16_t get_paj7620u2_state ( void ) { uint16_t Gesture_Data; Gesture_Data = DEV_I2C_ReadWord( PAJ_INT_FLAG1 ); if ( Gesture_Data ) { switch ( Gesture_Data ) { case PAJ_UP: LOG( "Up " ); return 0x0052; break; case PAJ_DOWN: LOG( "Down " ); return 0x0051; break; case PAJ_LEFT: LOG( "Left " ); return 0x0050; break; case PAJ_RIGHT: LOG( "Right " ); return 0x004f; break; case PAJ_FORWARD: LOG( "Forward " ); return 0x0000; break; case PAJ_BACKWARD: LOG( "Backward " ); return 0x0000; break; case PAJ_CLOCKWISE: LOG( "Clockwise " ); return 0x0000; break; case PAJ_COUNT_CLOCKWISE: LOG( "AntiClockwise " ); return 0x0000; break; case PAJ_WAVE: LOG( "Wave " ); return 0x0000; break; default: return 0x0000; break; } Gesture_Data = 0; } }
直接使用return 值来传参即,返回的值就是我在该手势下想要发送的键值。比如识别到向上的手势我就发送箭头向上的键值。
实验效果: