【GD32F470紫藤派开发板使用手册】第十三讲 USB_虚拟键盘实验

发布者:theta18最新更新时间:2024-11-19 来源: elecfans关键字:USB  虚拟键盘 手机看文章 扫描二维码
随时随地手机看文章

13.1实验内容

通过本实验主要学习以下内容:

  • USB协议基本原理

  • GD32F4xx USBFS的使用

  • 虚拟键盘的协议原理及使用

13.2实验原理

13.2.1USB通信基础知识

USB的全称是Universal Serial Bus,通用串行总线。它的出现主要是为了简化个人计算机与外围设备的连接,增加易用性。USB支持热插拔,并且是即插即用的,另外,它还具有很强的可扩展性,传输速度也很快,这些特性使支持USB接口的电子设备更易用、更大众化。GD32F4xx系列MCU集成了USB2.0全速OTG模块以及高速OTG模块。首先为大家介绍USB通信的一些基础知识,包括USB协议、枚举流程等,建议读者可以多多阅读USB协议,以更深入了解USB,USB官网链接如下,可参考:https://www.usb.org/

13.2.1.1USB金字塔型拓扑结构

塔顶为USB主控制器和根集线器(Root Hub),下面接USB集线器(Hub),集线器将一个USB口扩展为多个USB口,USB2.0规定集线器的层数最多为6层,理论上一个USB主控制器最多可接127个设备,因为协议规定USB设备具有一个7 bit的地址(取值范围为0~127,而地址0是保留给未初始化的设备使用的)。

wKgZomZJWwyAXF12AAGV9tCTOqI472.png

13.2.1.2NRZI编码

USB采用差分信号传输,使用的是如上图所示的NRZI编码方式:数据为0时,电平翻转;数据为1时,电平不翻转。如果出现6个连续的数据1,则插入一个数据0,强制电平翻转,以便时钟同步。上面的一条线表示的是原始数据序列,下面的一条线表示的是经过NRZI编码后的数据序列。

wKgZomZJWxWAQSVxAAIlErHJeKI886.png

13.2.1.3USB数据协议

USB数据是由二进制数据串组成,首先由数据串构成包(packet),包再构成事务(transaction),事务最终构成传输(transfer)。

USB传输的最小单位为包,一个包被分成不同的域,根据不同类型的包,所包含的域是不一样的,但是不同的包有个共同的特点,就是以包起始(SOP)开始,之后是同步域(0x00000001),然后是包内容,最后以包结束符(EOP)结束这个包。PID为标识域,由四位标识符加4位标识符反码构成,表明包的类型和格式。根据PID的不同,USB协议中规定的包类型有令牌包、数据包、握手包和特殊包等。

wKgaomZJWyGAdonqAAB8I0rvGFU350.png

USB事务通常有两个或三个包组成:令牌包、数据包和握手包,令牌包用来启动一个事务,总是由主机发送;数据包用来传输数据;握手包由数据接收者进行发送,表明数据的接收情况。批量、同步和中断传输每次传输都是一个事务,控制传输包括三个阶段:建立过程、数据过程和状态过程。

针对不同的数据传输场景,USB分为四种数据传输模式,这四种传输模式分别由不同的包(packet)组成,并且有不同的数据处理策略。每种数据传输模式的流程示意图以及应用场景如下:

  • 控制传输一般用于命令和状态的传输,分为控制读、控制写和无数据控制传输。在设备枚举的过程中,采用控制传输方式进行数据传输。

wKgaomZJWyyADNPjAACEVK-mK84554.png

  • 批量传输分为批量读和批量写,用于数据量大、对实时性要求不高的场合,如U盘。

wKgaomZJWzqAZO8uAAFUBWvFHOc025.png

  • 中断传输用于数据量小的场合,保证查询频率,如鼠标、键盘。

wKgZomZJW0eAXrKzAACbdpLmWDg195.png

  • 同步传输用于数据量大、同时对实时性要求较高的场合,如音视频。不保证数据完整性,没有ACK/NAK应答包,不进行数据重传。

wKgaomZJW1KAekrcAACaA0A3l5U676.png

13.2.1.4USB描述符

wKgZomZJW12AEgDpAAG50oNk3Wg291.png

  • 一个USB设备通常有一个或多个配置,但在同一时刻只能有一个配置;

  • 一个配置通常有一个或多个接口;

  • 一个接口通常有一个或多个端点;

在USB通信中,USB设备需要配置多个USB描述符用以枚举阶段将描述符返回给主机,用以主机的枚举以及识别。USB描述符包括设备描述符、配置描述符、接口描述符、端点描述符以及字符串描述符等。在GD32 USBD固件库中,针对各种描述符都按照USB协议定义了相关结构体,具体说明如下。

  • 设备描述符

每个设备必须有一个设备描述符,设备描述符提供了关于设备的配置、设备所归属的类、设备所遵循的协议代码、VID、PID等信息,其相关结构体定义如下。


C

typedef struct _usb_desc_dev {

usb_desc_header header; /*!< descriptor header, including type and size */

uint16_t bcdUSB; /*!< BCD of the supported USB specification */

uint8_t bDeviceClass; /*!< USB device class */

uint8_t bDeviceSubClass; /*!< USB device subclass */

uint8_t bDeviceProtocol; /*!< USB device protocol */

uint8_t bMaxPacketSize0; /*!< size of the control (address 0) endpoint's bank in bytes */

uint16_t idVendor; /*!< vendor ID for the USB product */

uint16_t idProduct; /*!< unique product ID for the USB product */

uint16_t bcdDevice; /*!< product release (version) number */

uint8_t iManufacturer; /*!< string index for the manufacturer's name */

uint8_t iProduct; /*!< string index for the product name/details */

uint8_t iSerialNumber; /*!< string index for the product's globally unique hexadecimal serial number */

uint8_t bNumberConfigurations; /*!< total number of configurations supported by the device */

} usb_desc_dev;

配置描述符

每个USB设备都至少具有一个配置描述符,在设备描述符中规定了该设备有多少种配置,每种配置都有一个描述符,其相关结构体定义如下。


C

typedef struct _usb_desc_config {

usb_desc_header header; /*!< descriptor header, including type and size */

uint16_t wTotalLength; /*!< size of the configuration descriptor header, and all sub descriptors inside the configuration */

uint8_t bNumInterfaces; /*!< total number of interfaces in the configuration */

uint8_t bConfigurationValue; /*!< configuration index of the current configuration */

uint8_t iConfiguration; /*!< index of a string descriptor describing the configuration */

uint8_t bmAttributes; /*!< configuration attributes */

uint8_t bMaxPower; /*!< maximum power consumption of the device while in the current configuration */

} usb_desc_config;

接口描述符

接口描述符用以描述接口信息,接口描述符不能单独返回,必须附着在配置描述符后一并返回,其相关结构体定义如下。


C

typedef struct _usb_desc_itf {

usb_desc_header header; /*!< descriptor header, including type and size */

uint8_t bInterfaceNumber; /*!< index of the interface in the current configuration */

uint8_t bAlternateSetting; /*!< alternate setting for the interface number */

uint8_t bNumEndpoints; /*!< total number of endpoints in the interface */

uint8_t bInterfaceClass; /*!< interface class ID */

uint8_t bInterfaceSubClass; /*!< interface subclass ID */

uint8_t bInterfaceProtocol; /*!< interface protocol ID */

uint8_t iInterface; /*!< index of the string descriptor describing the interface */

} usb_desc_itf;

端点描述符

端点描述符用以描述端点信息,端点描述符不能单独返回,必须附着在配置描述符后一并返回,其相关结构体定义如下。


字符串描述符

字符串描述符可含有指向描述制造商、产品、序列号、配置和接口的字符串的索引。类和制造商专属描述符可含有指向额外字符串描述符的索引。对字符串描述符的支持是可选的,有些类可能会需要它们。


C

typedef struct _usb_desc_str {

usb_desc_header header; /*!< descriptor header, including type and size. */

uint16_t unicode_string[64]; /*!< unicode string data */

} usb_desc_str;


13.2.1.5USB枚举过程

USB枚举实际上是host检测到device插入后,通过发送各种标准请求,请device返回各种USB描述符的过程。USB枚举的示意图如下:

wKgaomZJW7yAXYOoAAH64GaWpyY119.png

13.2.2GD32 USBD模块简介

GD32F4xx系列MCU提供了一个USB2.0全速USBFS OTG接口模块和一个USB2.0高速USBHS接口模块,其中,USBHS若需要使用高速接口,需要外接USBHS高速PHY芯片,如果不外借高速PHY,其可以作为USBFS接口,因而GD32F4XX在不接外部高速PHY的情况下,其可以使用两个USBFS接口。

USBFS支持USB 2.0协议所定义的四种传输类型(控制、批量、中断和同步传输),本开发板使用的是USBFS接口,以下为大家介绍USBFS接口功能。

USBFS主要特性如下:

n支持USB 2.0全速(12Mb/s)/低速(1.5Mb/s)主机模式;

n支持USB 2.0全速(12Mb/s) 设备模式;

n支持遵循HNP(主机协商协议)和SRP(会话请求协议)的OTG协议;

n支持所有的4种传输方式:控制传输、批量传输、中断传输和同步传输;

n在主机模式下,包含USB事务调度器,用于有效地处理USB事务请求;

n包含一个1.25KB的FIFO RAM;

n在主机模式下,支持8个通道;

n在主机模式下,包含2个发送FIFO(周期性发送FIFO和非周期性发送FIFO)和1个接收

FIFO(由所有的通道共享);

n在设备模式下,包含4个发送FIFO(每个IN端点一个发送FIFO)和1个接收FIFO(由所有

的OUT端点共享);

n在设备模式下, 支持4个OUT端点和4个IN端点;

n在设备模式下,支持远程唤醒功能;

n包含一个支持USB协议的全速USB PHY;

n在主机模式下,SOF的时间间隔可动态调节;

n可将SOF脉冲输出到PAD;

n可检测ID引脚电平和VBUS电压;

n在主机模式或者OTG A设备模式下,需要外部部件为连接的USB设备提供电源。

USBD模块框图如下所示。

wKgZomZJW8qAQM1ZAAFeaVLZSeg329.png

13.2.3USBFS固件库说明

USBFS 固件库使用指南可以参考官网相关文档,下载地址如下:https://www.gd32mcu.com/download/down/document_id/372/path_type/2

wKgZomZJW9eASJKTAADFYcxM7qM927.png

13.3硬件设计

GD32F470紫藤派开发板的USB通信接口选择的是目前较为通用的Type C接口,读者手中的用于手机充电的Type C通信线即可使用。

wKgZomZJW-WAEdxUAAGNF-C5ieI167.png

13.4代码解析


本例程主要实现通过按键向PC发送键值的现象,实现模拟键盘的效果。


本例程主函数如下所示。


C

int main(void)

{

usb_gpio_config();

usb_rcu_config();

usb_timer_init();


hid_itfop_register (&hid_keyboard, &fop_handler);


usbd_init (&hid_keyboard,

#ifdef USE_USB_FS

USB_CORE_ENUM_FS,

#elif defined(USE_USB_HS)

USB_CORE_ENUM_HS,

#endif

&hid_desc,

&usbd_hid_cb);


usb_intr_config();


/* check if USB device is enumerated successfully */

while (USBD_CONFIGURED != hid_keyboard.dev.cur_status) {

}


while (1) {

fop_handler.hid_itf_data_process(&hid_keyboard);

}

}

gpio的配置如下,定义为全速模式,主要配置PA11和PA12引脚。


C

void usb_gpio_config(void)

{

rcu_periph_clock_enable(RCU_SYSCFG);


#ifdef USE_USB_FS


rcu_periph_clock_enable(RCU_GPIOA);


/* USBFS_DM(PA11) and USBFS_DP(PA12) GPIO pin configuration */

gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11 | GPIO_PIN_12);

gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_11 | GPIO_PIN_12);


gpio_af_set(GPIOA, GPIO_AF_10, GPIO_PIN_11 | GPIO_PIN_12);


#elif defined(USE_USB_HS)


#ifdef USE_ULPI_PHY

rcu_periph_clock_enable(RCU_GPIOA);

rcu_periph_clock_enable(RCU_GPIOB);

rcu_periph_clock_enable(RCU_GPIOC);

rcu_periph_clock_enable(RCU_GPIOH);

rcu_periph_clock_enable(RCU_GPIOI);


/* ULPI_STP(PC0) GPIO pin configuration */

gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_0);

gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_0);


/* ULPI_CK(PA5) GPIO pin configuration */

gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_5);

gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_5);


/* ULPI_NXT(PH4) GPIO pin configuration */

gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_4);

gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_4);


/* ULPI_DIR(PI11) GPIO pin configuration */

gpio_mode_set(GPIOI, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11);

gpio_output_options_set(GPIOI, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_11);


/* ULPI_D1(PB0), ULPI_D2(PB1), ULPI_D3(PB10), ULPI_D4(PB11)

ULPI_D5(PB12), ULPI_D6(PB13) and ULPI_D7(PB5) GPIO pin configuration */

gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE,

GPIO_PIN_5 | GPIO_PIN_13 | GPIO_PIN_12 |

GPIO_PIN_11 | GPIO_PIN_10 | GPIO_PIN_1 | GPIO_PIN_0);

gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_MAX,

GPIO_PIN_5 | GPIO_PIN_13 | GPIO_PIN_12 |

GPIO_PIN_11 | GPIO_PIN_10 | GPIO_PIN_1 | GPIO_PIN_0);


/* ULPI_D0(PA3) GPIO pin configuration */

gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_3);

gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_3);


gpio_af_set(GPIOC, GPIO_AF_10, GPIO_PIN_0);

gpio_af_set(GPIOH, GPIO_AF_10, GPIO_PIN_4);

gpio_af_set(GPIOI, GPIO_AF_10, GPIO_PIN_11);

gpio_af_set(GPIOA, GPIO_AF_10, GPIO_PIN_5 | GPIO_PIN_3);

gpio_af_set(GPIOB, GPIO_AF_10, GPIO_PIN_5 | GPIO_PIN_13 | GPIO_PIN_12 |

GPIO_PIN_11 | GPIO_PIN_10 | GPIO_PIN_1 | GPIO_PIN_0);

#elif defined(USE_EMBEDDED_PHY)

rcu_periph_clock_enable(RCU_GPIOB);


/* USBHS_DM(PB14) and USBHS_DP(PB15) GPIO pin configuration */

gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_14 | GPIO_PIN_15);

gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_14 | GPIO_PIN_15);

gpio_af_set(GPIOB, GPIO_AF_12, GPIO_PIN_14 | GPIO_PIN_15);

#endif /* USE_ULPI_PHY */


#endif /* USE_USBFS */

}

rcu的配置如下,主要用于配置USB时钟,USB需要一个稳定的48M时钟。


C

void usb_rcu_config(void)

{

#ifdef USE_USB_FS

rcu_pll48m_clock_config(RCU_PLL48MSRC_PLLQ);

rcu_ck48m_clock_config(RCU_CK48MSRC_PLL48M);


rcu_periph_clock_enable(RCU_USBFS);

#elif defined(USE_USB_HS)

#ifdef USE_EMBEDDED_PHY

rcu_pll48m_clock_config(RCU_PLL48MSRC_PLLQ);

rcu_ck48m_clock_config(RCU_CK48MSRC_PLL48M);

#elif defined(USE_ULPI_PHY)

rcu_periph_clock_enable(RCU_USBHSULPI);

#endif /* USE_EMBEDDED_PHY */


rcu_periph_clock_enable(RCU_USBHS);

#endif /* USB_USBFS */

}

Usb timer的配置如下,主要用于延迟。


C

void usb_timer_init (void)

{

/* configure the priority group to 2 bits */

nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);


/* enable the TIMER2 global interrupt */

nvic_irq_enable((uint8_t)TIMER2_IRQn, 1U, 0U);


rcu_periph_clock_enable(RCU_TIMER2);

}

注册HID接口操作函数如下所示。在该代码清单中,注册了HID接口操作的配置以及数据处理函数句柄,用于后续函数调用。


C

uint8_t hid_itfop_register (usb_dev *udev, hid_fop_handler *hid_fop)

{

if (NULL != hid_fop) {

udev->dev.user_data = (void *)hid_fop;


return USBD_OK;

}


return USBD_FAIL;

}

USBD内核初始化函数如下所示。在该代码清单中,首先配置设备类callback函数,之后创建字符串,配置USB以及初始化USB内核,断开USB连接,初始化USB设备模式,之后设置USB连接,将USB连接状态配置为DEFAULT默认状态,启动状态机。


C

void usbd_init (usb_core_driver *udev, usb_core_enum core, usb_desc *desc, usb_class_core *class_core)

{

udev->dev.desc = desc;


/* class callbacks */

udev->dev.class_core = class_core;


/* create serial string */

serial_string_get(udev->dev.desc->strings[STR_IDX_SERIAL]);

[1] [2]
关键字:USB  虚拟键盘 引用地址:【GD32F470紫藤派开发板使用手册】第十三讲 USB_虚拟键盘实验

上一篇:GD32F303固件库开发(8)----USART收发配置
下一篇:最后一页

小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved