UEFI开发探索85- YIE002USB开发板(08 制作HID设备)

(请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365)

YIE002USB开发板之制作HID设备-编程

  • 1 YIE002-STM32的USB编程
  • 2 调整示例工程Custom_HID
  • 3 修改代码
    • 3.1 准备描述符
    • 3.2 支持ReadFile()和WriteFile()方式的代码
    • 3.3 支持Input报告和Output报告的方式、以及Feature报告的代码
      • 3.3.1 准备通信用的标志和缓冲区
      • 3.3.2 修改RESULT CustomHID_Data_Setup(uint8_t RequestNo)
      • 3.3.3 修改Get_Report和Set_Report的处理函数
  • 4 测试


在介绍完所有背景知识后,终于可以进入实质的嵌入式编程了。

本篇拟使用YIE002开发板,制作一个USB HID设备,支持三种通信方式,以对应UEFI开发探索73和74所讨论的三种上位机通信。

这个系列的博客,主要还是偏向UEFI编程的探索,对于嵌入式的编程,不想讨论过多。对于YIE002的嵌入式开发,请移步我另外一个专栏“嵌入式开发”,在其中我开了一个新坑,用来探索YIE002的编程。

1 YIE002-STM32的USB编程

这款开发板的主芯片是STM32F103C8T6,是我出差最常带的版型。本篇所写的代码,适用于所有F1系列作为主芯片的开发板,比如正点原子的战舰开发板。

从意法的官方资料可知,STM32 MCU有如下USB IP:

  1. USB IP 可作为全速USB设备,存在于STM32F102、STM32F103;
  2. USB+ IP 可作为全速USB设备,存在于STM32F0x2;
  3. FS OTG IP 可作为全速和低速USB主机、全速USB设备,存在于STM32F105、STM32F107、STM32F2、STM32F4;
  4. HS OTG IP 可作为高速、全速和低速USB主机,可作为高速和全速USB设备,存在于STM32F2、STM32F4。

官方也提供了不同的USB库,以适应开发需求。比如针对USB IP和USB+ IP,提供的库如图1所示。
USB(+) IP对应的USB库
图1 USB(+) IP对应的USB库

因此,对F103系的MCU而言,可以选择Legacy library进行开发,也可以使用Cube library进行开发。

本篇的开发,是基于Legacy library的示例工程Custom_HID改造的。使用Cube library开发的方式,可以博客中参考嵌入式开发的专栏。

2 调整示例工程Custom_HID

我平常开发嵌入式产品,主要使用的是MDK Keil工具。Legacy library(STSW-STM32121)中提供的示例工程,支持各种编译工具。当然,也带来很多我不需要的冗余代码。另外,由于共用了外围设备库和评估板的各种文件,Legacy library组织了很好的代码结构,但对我而言是累赘,总不能每次都在库的文件夹下进行修改吧。

因此,我对示例工程Custom_HID进行了调整,删除了很多不需要的代码,重新组织了代码结构。

如图2所示,是调整过的代码结构。
调整过的代码结构
图2 调整过的代码结构

另一个需要注意的地方,是头文件的路径,如图3所示。
头文件包含路径
图3 头文件包含路径

另外,在选择设备的时候,选择STM32F103C8,编译针对YIE002-STM32型开发板的生成文件。

其他的细节,请以本篇博客提供的工程,对照原始的Custom_HID工程,即可了解。

3 修改代码

修改代码的过程主要包括:

  1. 准备各种描述符,包括设备描述符、配置描述符和报表描述符等;
  2. 实现三种通信方式的代码支持;

详细过程如下。

3.1 准备描述符

描述符主要在文件usb_desc.c中修改。配置描述符比较简单,主要将厂商ID和产品ID修改为自己需要的值。主要的修改在配置描述符中,需要注意的是,端点描述符和接口描述符与配置描述符在同一数组内。

示例1 配置描述符

const uint8_t CustomHID_ConfigDescriptor[CUSTOMHID_SIZ_CONFIG_DESC] =
{
……//前略
/******************** Descriptor of Custom HID endpoints ******************/
    /* 27 */
    0x07,          /* bLength: Endpoint Descriptor size */
    USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */

    0x81,          /* bEndpointAddress: Endpoint Address (IN) */
    0x03,          /* bmAttributes: Interrupt endpoint */
    0x40,          /* wMaxPacketSize: 64 Bytes max */
    0x00,
    0x20,          /* bInterval: Polling Interval (32 ms) */
    /* 34 */
    	
    0x07,	/* bLength: Endpoint Descriptor size */
    USB_ENDPOINT_DESCRIPTOR_TYPE,	/* bDescriptorType: */
			/*	Endpoint descriptor type */
    0x01,	/* bEndpointAddress: */
			/*	Endpoint Address (OUT) */
    0x03,	/* bmAttributes: Interrupt endpoint */
    0x40,	/* wMaxPacketSize: 64 Bytes max  */
    0x00,
0x20,	/* bInterval: Polling Interval (20 ms) */
}

从示例1可以看出,通信用的端点号为1,可以输入输出。包括缺省的端点0在内,使用了两个端点号,因此在接口描述符中,bNumEndpoints必须为2(示例1中没有列出)。

而报表描述符,设置了16字节的通道,如示例2所示。

示例2 报表描述符

const uint8_t CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
{
	0x05, 0x01, // USAGE_PAGE (Generic Desktop)
	0x09, 0x00, // USAGE (0) 
	0xa1, 0x01, // COLLECTION (Application)
	0x15, 0x00, //     LOGICAL_MINIMUM (0)
	0x25, 0xff, //     LOGICAL_MAXIMUM (255)
	0x19, 0x01, //     USAGE_MINIMUM (1)
	0x29, 0x10, //     USAGE_MAXIMUM (16) 
	0x95, 0x10, //     REPORT_COUNT (16)
	0x75, 0x08, //     REPORT_SIZE (8)
	0x81, 0x02, //     INPUT (Data,Var,Abs)
	0x19, 0x01, //     USAGE_MINIMUM (1)
	0x29, 0x10, //     USAGE_MAXIMUM (16) 
	0x91, 0x02, //   OUTPUT (Data,Var,Abs)
	0x19, 0x01, //     USAGE_MINIMUM (1)
	0x29, 0x10, //     USAGE_MAXIMUM (16)
	0xB1, 0x02, // Feature(Data, Variable, Absolute)
	0xc0        // END_COLLECTION
}; /* CustomHID_ReportDescriptor */

从中可以看出,构建的通道中,Input报告、Output报告和Feature报告均为16字节长。

3.2 支持ReadFile()和WriteFile()方式的代码

在源文件usb_prop.c中,修改端点的通信能力:

  SetEPTxCount(ENDP1, 0x40);   //robin: 修改为64字节大小
  SetEPRxCount(ENDP1, 0x40);   //robin: 修改为64字节大小

修改通信函数,在源文件usb_endp.c中进行修改,代码如下:

uint8_t Receive_Buffer[0xff];
void EP1_OUT_Callback(void)
{
//  BitAction Led_State;
  uint32_t DataLength = 0;
  /* Read received data (2 bytes) */  
  DataLength=USB_SIL_Read(EP1_OUT, Receive_Buffer); //读取端点得到的数据
  SetEPRxStatus(ENDP1, EP_RX_VALID);
  
  if (Receive_Buffer[0] == 0xA0)//将第二个字节改为1返回,表示是采用端点发送的方式
	{
		Receive_Buffer[1]=0x1;
	}
  USB_SIL_Write(EP1_IN,Receive_Buffer,DataLength);
  SetEPTxStatus(ENDP1,EP_TX_VALID);
}

其他代码不用动,并将usb_prop.c下的 void CustomHID_Status_In(void)函数中的内容全部注释掉。

至此,就完成了这种通信方式的编写。

3.3 支持Input报告和Output报告的方式、以及Feature报告的代码

为了支持这两种通信方式,需要实现Set_Report和Get_Report类命令。这两个类命令,有三种报告使用,包括Input报告、Output报告和Feature报告。其中,Input报告和Output报告作为输入输出通信,对应上层的函数为HidD_GetInputReport()和HidD_SetOutputReport();Feature报告对应上层的函数为HidD_GetFeature()和HidD_SetFeature()。

这三种报告都是使用Set_Report 和Get_Report命令来传输数据的,只是通过类命令中的wValue来区分报告类型:1为Input报告、2为Output报告、3为Feature报告。

需要注意的是,USB通信中,都是站在主机的角度来看通信过程的,所有命令也都是由主机发起的。Set_Report对设备而言,是接收数据;Get_Report对设备而言,是发送数据。

修改过程如下:

3.3.1 准备通信用的标志和缓冲区

uint8_t Report_Buf[16];     //Robin: 报告长度为16,见报告描述符 
uint8_t Report_InOut_Flag=0; //Robin: Input报告和Output报告标志
uint8_t Report_Feature_Flag=0;//Robin: Feature报告标志

3.3.2 修改RESULT CustomHID_Data_Setup(uint8_t RequestNo)

......
/*** GET_PROTOCOL, GET_REPORT, SET_REPORT ***/
  else if ( (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)) )
  {         
    switch( RequestNo )
    {
    case GET_PROTOCOL:
      CopyRoutine = CustomHID_GetProtocolValue;
      break;
    case SET_REPORT:
      CopyRoutine = CustomHID_SetReport_Feature;
      Request = SET_REPORT;
      break;
    //robin add for get_report
    case GET_REPORT:
      if((Report_InOut_Flag==0)&&(Report_Feature_Flag==0))
        return USB_NOT_READY;   //Robin: Inform the host that the data is not ready
      CopyRoutine = CustomHID_GetReport_Feature;
      Request = GET_REPORT;
      break;
    default:
      break;
    }
  }
  
  if (CopyRoutine == NULL)
  {
    return USB_UNSUPPORT;
  }
  
  pInformation->Ctrl_Info.CopyData = CopyRoutine;
  pInformation->Ctrl_Info.Usb_wOffset = 0;
  (*CopyRoutine)(0);
  return USB_SUCCESS;
}

也即增加了Get_Report和Set_Report的处理函数。

3.3.3 修改Get_Report和Set_Report的处理函数

/*******************************************************************************
* Function Name  : CustomHID_SetReport_Feature
* Description    : Set Feature request handling
* Input          : Length.
* Output         : None.
* Return         : Buffer
*******************************************************************************/
uint8_t *CustomHID_SetReport_Feature(uint16_t Length)
{
  if(pInformation->USBwValues.bw.bb1 == OUT_REPORT)
    Report_InOut_Flag=1;
  else if(pInformation->USBwValues.bw.bb1 == FEATURE_REPORT)
    Report_Feature_Flag=1;
  
  if (Length == 0)
  {
    pInformation->Ctrl_Info.Usb_wLength = 16;//2; //robin
    return NULL;
  }
  else
  {
//    return Report_Buf;
    return &Report_Buf[pInformation->Ctrl_Info.Usb_wOffset];
  }
}
/*************************************************************************
* Function Name  : CustomHID_GetReport_Feature
* Description    : Set Feature request handling
* Input          : Length.
* Output         : None.
* Return         : Buffer
*************************************************************************/
uint8_t *CustomHID_GetReport_Feature(uint16_t Length)
{
  if(pInformation->USBwValues.bw.bb1 == IN_REPORT)
    Report_InOut_Flag=0;
  else if(pInformation->USBwValues.bw.bb1 == FEATURE_REPORT)
    Report_Feature_Flag=0;
  
  if (Length == 0) //此处报告需要发送的长度
  {
    pInformation->Ctrl_Info.Usb_wLength = 16;//2; //robin
    return NULL;
//    return (uint8_t *)16;
  }
  else  //此处返回需要处理的数据
  {
    if(pInformation->USBwValues.bw.bb1 == FEATURE_REPORT)
    {
      if(Report_Buf[0] == 0xA0)
        Report_Buf[1]=0x3;
    }
    else
    {
      if(Report_Buf[0] == 0xA0)
        Report_Buf[1]=0x2;
    }
    return Report_Buf;
  }
}

从中很容易看出,针对不同的报告,函数进行了不同的处理。为了方便上位机测试,简单地将收到的数据,进行了局部修改,然后返回。

4 测试

将固件代码编译,下载到YIE002开发板中,使用上位机工具进行测试,如图4所示。
使用UsbHID工具测试HID设备
图4 使用UsbHID工具测试HID设备

根据HID设备的固件代码,不同的通信方式,在第一个字节为0xA0的时候,返回的第二个字节分别为0x1、0x2和0x3。从图3中可以看出,是按照代码设定的逻辑在正常工作的。

至此,我们完成了USB HID设备的编写。下一篇开始,将在UEFI环境下对HID设备进行访问。

Gitee地址:https://gitee.com/luobing4365/uefi-exolorer
项目代码位于:/84 YIE2HID下


热门文章

暂无图片
编程学习 ·

Java输出数组的内容

Java输出数组的内容_一万个小时-CSDN博客_java打印数组内容1. 输出内容最常见的方式// List<String>类型的列表List<String> list new ArrayList<String>();list.add("First");list.add("Second");list.add("Third");list.ad…
暂无图片
编程学习 ·

母螳螂的“魅惑之术”

在它们对大蝗虫发起进攻的时候&#xff0c;我认认真真地观察了一次&#xff0c;因为它们突然像触电一样浑身痉挛起来&#xff0c;警觉地面对限前这个大家伙&#xff0c;然后放下自己优雅的身段和祈祷的双手&#xff0c;摆出了一个可怕的姿势。我被眼前的一幕吓到了&#xff0c;…
暂无图片
编程学习 ·

疯狂填词 mad_libs 第9章9.9.2

#win7 python3.7.0 import os,reos.chdir(d:\documents\program_language) file1open(.\疯狂填词_d9z9d2_r.txt) file2open(.\疯狂填词_d9z9d2_w.txt,w) words[ADJECTIVE,NOUN,VERB,NOUN] str1file1.read()#方法1 for word in words :word_replaceinput(fEnter a {word} :)str1…
暂无图片
编程学习 ·

HBASE 高可用

为了保证HBASE是高可用的,所依赖的HDFS和zookeeper也要是高可用的. 通过参数hbase.rootdir指定了连接到Hadoop的地址,mycluster表示为Hadoop的集群. HBASE本身的高可用很简单,只要在一个健康的集群其他节点通过命令 hbase-daemon.sh start master启动一个Hmaster进程,这个Hmast…
暂无图片
编程学习 ·

js事件操作语法

一、事件的绑定语法 语法形式1 事件监听 标签对象.addEventListener(click,function(){}); 语法形式2 on语法绑定 标签对象.onclick function(){} on语法是通过 等于赋值绑定的事件处理函数 , 等于赋值本质上执行的是覆盖赋值,后赋值的数据会覆盖之前存储的数据,也就是on…
暂无图片
编程学习 ·

Photoshop插件--晕影动态--选区--脚本开发--PS插件

文章目录1.插件界面2.关键代码2.1 选区2.2 动态晕影3.作者寄语PS是一款栅格图像编辑软件&#xff0c;具有许多强大的功能&#xff0c;本文演示如何通过脚本实现晕影动态和选区相关功能&#xff0c;展示从互联网收集而来的一个小插件&#xff0c;供大家学习交流&#xff0c;请勿…
暂无图片
编程学习 ·

vs LNK1104 无法打开文件“xxx.obj”

写在前面&#xff1a; 向大家推荐两本新书&#xff0c;《深度学习计算机视觉实战》和《学习OpenCV4&#xff1a;基于Python的算法实战》。 《深度学习计算机视觉实战》讲了计算机视觉理论基础&#xff0c;讲了案例项目&#xff0c;讲了模型部署&#xff0c;这些项目学会之后可以…
暂无图片
编程学习 ·

工业元宇宙的定义与实施路线图

工业元宇宙的定义与实施路线图 李正海 1 工业元宇宙 给大家做一个关于工业元宇宙的定义。对于工业&#xff0c;从设计的角度来讲&#xff0c;现在的设计人员已经做到了普遍的三维设计&#xff0c;但是进入元宇宙时代&#xff0c;就不仅仅只是三维设计了&#xff0c;我们的目…
暂无图片
编程学习 ·

【leectode 2022.1.15】完成一半题目

有 N 位扣友参加了微软与力扣举办了「以扣会友」线下活动。主办方提供了 2*N 道题目&#xff0c;整型数组 questions 中每个数字对应了每道题目所涉及的知识点类型。 若每位扣友选择不同的一题&#xff0c;请返回被选的 N 道题目至少包含多少种知识点类型。 示例 1&#xff1a…
暂无图片
编程学习 ·

js 面试题总结

一、js原型与原型链 1. prototype 每个函数都有一个prototype属性&#xff0c;被称为显示原型 2._ _proto_ _ 每个实例对象都会有_ _proto_ _属性,其被称为隐式原型 每一个实例对象的隐式原型_ _proto_ _属性指向自身构造函数的显式原型prototype 3. constructor 每个prot…
暂无图片
编程学习 ·

java练习代码

打印自定义行数的空心菱形练习代码如下 import java.util.Scanner; public class daYinLengXing{public static void main(String[] args) {System.out.println("请输入行数");Scanner myScanner new Scanner(System.in);int g myScanner.nextInt();int num g%2;//…
暂无图片
编程学习 ·

RocketMQ-什么是死信队列?怎么解决

目录 什么是死信队列 死信队列的特征 死信消息的处理 什么是死信队列 当一条消息初次消费失败&#xff0c;消息队列会自动进行消费重试&#xff1b;达到最大重试次数后&#xff0c;若消费依然失败&#xff0c;则表明消费者在正常情况下无法正确地消费该消息&#xff0c;此时…
暂无图片
编程学习 ·

项目 cg day04

第4章 lua、Canal实现广告缓存 学习目标 Lua介绍 Lua语法 输出、变量定义、数据类型、流程控制(if..)、循环操作、函数、表(数组)、模块OpenResty介绍(理解配置) 封装了Nginx&#xff0c;并且提供了Lua扩展&#xff0c;大大提升了Nginx对并发处理的能&#xff0c;10K-1000K Lu…
暂无图片
编程学习 ·

输出三角形

#include <stdio.h> int main() { int i,j; for(i0;i<5;i) { for(j0;j<i;j) { printf("*"); } printf("\n"); } }
暂无图片
编程学习 ·

stm32的BOOTLOADER学习1

序言 最近计划学习stm32的BOOTLOADER学习,把学习过程记录下来 因为现在网上STM32C8T6还是比较贵的,根据我的需求flash空间小一些也可以,所以我决定使用stm32c6t6.这个芯片的空间是32kb的。 #熟悉芯片内部的空间地址 1、flash ROM&#xff1a; 大小32KB&#xff0c;范围&#xf…
暂无图片
编程学习 ·

通过awk和shell来限制IP多次访问之学不会你打死我

学不会你打死我 今天我们用shell脚本&#xff0c;awk工具来分析日志来判断是否存在扫描器来进行破解网站密码——限制访问次数过多的IP地址&#xff0c;通过Iptables来进行限制。代码在末尾 首先我们要先查看日志的格式&#xff0c;分析出我们需要筛选的内容&#xff0c;日志…
暂无图片
编程学习 ·

Python - 如何像程序员一样思考

在为计算机编写程序之前&#xff0c;您必须学会如何像程序员一样思考。学习像程序员一样思考对任何学生都很有价值。以下步骤可帮助任何人学习编码并了解计算机科学的价值——即使他们不打算成为计算机科学家。 顾名思义&#xff0c;Python经常被想要学习编程的人用作第一语言…
暂无图片
编程学习 ·

蓝桥杯python-数字三角形

问题描述 虽然我前后用了三种做法&#xff0c;但是我发现只有“优化思路_1”可以通过蓝桥杯官网中的测评&#xff0c;但是如果用c/c的话&#xff0c;每个都通得过&#xff0c;足以可见python的效率之低&#xff08;但耐不住人家好用啊&#xff08;哭笑&#xff09;&#xff09…