结果帖
论坛里由很多大佬们都添加成功的添加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 值来传参即,返回的值就是我在该手势下想要发送的键值。比如识别到向上的手势我就发送箭头向上的键值。
实验效果:


 
					
				
 
			
			
			
						
			 
					
				 
					
				 
					
				 
					
				 
					
				 
					
				 我要赚赏金
 我要赚赏金 STM32
STM32 MCU
MCU 通讯及无线技术
通讯及无线技术 物联网技术
物联网技术 电子DIY
电子DIY 板卡试用
板卡试用 基础知识
基础知识 软件与操作系统
软件与操作系统 我爱生活
我爱生活 小e食堂
小e食堂

