【GD32】从零开始学GD32单片机 | USB通用串行总线接口+HID键盘例程(GD32F470ZGT6)

本文主要是介绍【GD32】从零开始学GD32单片机 | USB通用串行总线接口+HID键盘例程(GD32F470ZGT6),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 简介

        USB,全称通用串行总线,相信大家都非常熟悉了,日常生活只要用到手机电脑都离不开这个接口,像鼠标键盘U盘都需要使用这个接口进行数据传输,下面简单介绍一下。

1.1 版本标准

        USB的标准总体可以分为低速、全速和高速,分别对应USB 1.0、USB 1.1和USB 2.0版本;当然后面推出了USB 3.0、USB 3.1和目前最新的USB4标准,下面的表格列出了各个USB版本的差异。

USB标准理论速度
USB 1.01.5Mbps
USB 1.112Mbps
USB 2.0480Mbps
USB 3.05Gbps
USB 3.110Gbps
USB450Gbps

        在GD32F4系列芯片中,内部搭载了USB全速和高速接口,因此是可以使用USB 2.0及以下的标准。

        但USB的工作光有接口还不行,必须还要对应的PHY才行,GD32F4内部自带有USB全速PHY,但没有USB高速PHY,所以如果要使用高速USB得在外部硬件电路上添加对应的PHY芯片。所以后面的例程会使用USB的全速标准。

1.2 接口

        经过几十年的发展,USB衍生出了众多接口,像我们常用的有USB Type-A和USB Type-C接口。最简单的USB接口只需要4根线即可——电源线(VBUS)、地线(GND)、差分正(DP)和差分负(DP)。

        USB为了实现高速的数据传输,是使用差分信号进行通讯的,差分信号具有非常优秀的抗干扰性。在差分通讯中,DP线电压高于DM线电压,代表逻辑1;反之,DP线低于DM线电压,则代表逻辑0。不过,在编程中我们是不需要关心这个的,因为PHY电路会自动为我们处理这些信号。

        随着USB的速度越来越快,显然一对差分线就不能满足了,所以USB 2.0以上的USB接口就需要三对差分线进行数据的传输,下面就是USB 3.0接口的管脚定义。

1.3 设备类

        使用USB协议的设备众多,显然、每种类型的设备需要传输的数据是不同的,因此USB给每一类的设备定义了对应的设备类(class)。像鼠标、键盘使用的是HID设备类,U盘等存储介质使用的是MSC设备类,同时USB也可以配置成虚拟串口,使用的是CDC设备类

1.4 通讯

        USB是一种热插拔接口,因此在用户插入设备后主机和设备会有一系列的通讯过程,来配置USB的工作环境,之后才能够进行对应的数据传输。

1.4.1 枚举

        USB通讯前,主机需要了解怎么与插入的这个设备交流,因此需要有一个枚举的过程,配置相关的信息。

        USB设备插入主机,HUB初始化成功后主机会为设备供电,此时设备进入默认状态;接着主机给设备分配地址,进行基本的配置,配置过程一般就是设备告诉主机自己的名字、PID、VID、支持的设备类、供电能力等等信息;每种设备类需要提供主机的信息是不同的,具体可以在USB官网下载对应的文档研究。

1.4.2 传输类型

        USB一共有4种数据传输类型——中断传输、同步传输、控制传输和批量传输

        1. 中断传输。低速率,固定延迟。HID设备的典型传输方式。

        2. 同步传输。周期、连续的主从信息传递,常用于与时间相关的数据。多用于传输视频帧数据。

        3. 控制传输。突发、非周期的由主机发起的通讯,设备的枚举过程就是使用控制传输。

        4. 批量传输。非周期、大块数据的突发通讯,MSC设备的典型传输方式。

1.4.3 管道、接口和端点

        USB的通讯逻辑由管道、接口和端点组成。

        USB通讯的最基本单元是端点(Endpoint),分为输入端点和输出端点,无论是数据还是命令都是通过端点进行传输的;其中端点0是专门用于控制传输的,像枚举过程、主机命令下发都使用端点0;其他的端点的话就可以自定义。

        接口(Interface)可以理解为一组端点的集合,它是面向功能而言的。就比如说,我这个设备既支持鼠标操作又支持键盘操作,那么相当于这个设备就有两个功能,所以接口也对应有两个。

        管道(Pipe)是用来联系端点与主机软件,它决定数据如何在主机和设备间传输,所以数据在端点的每次传输都要建立管道实现。管道又分为流管道和消息管道;流管道用于传输与USB规范无关的数据,如用户数据;消息管道用于传输包含USB规范的数据。

2. 时钟

        USB工作需要48MHz的时钟,在GD32F4系列中,USB时钟可以由内部的RC 48MHz震荡器或PLL锁相环分频得到,一般都会使用RC震荡器(下面时钟树红线路径),因为这个震荡器是带CTC模块的,即可以自动对时钟进行校准。

3. 例程

        例程会初始化一个基于HID设备类的键盘,当按下板子上的按钮会向电脑发送键位‘A’。

3.1 HID设备

        简单介绍一下例程中涉及到的HID设备类,HID全称人机交互接口,像我们常用的键盘、鼠标、触摸板、手柄等交互类设备都是使用HID。

        在设备的枚举过程中HID设备需要提供物理描述符和汇报描述符;物理描述符是可选的,它主要描述这个设备是由人体的哪个或哪些部位所使用的;汇报描述符是必要的,而且非常重要,它描述数据的组织排列方式,主机是通过汇报描述符提供的信息来解析消息或构建数据包的。

        不过汇报描述符的格式在这里就不介绍了,要讲的话另开一篇都讲不完,官方文档多达一千多页,而且是全英文的,感兴趣的同学可以下载研究研究。

3.2 枚举过程

        USB的枚举都是基于描述符的,描述符在代码中其实就是一个个数组,我们需要根据官方文档中的协议规范往里面填数据。

        HID的枚举过程,首先发送设备描述符(Device Descriptor),里面一般包含PID、VID、序列号等信息;接着发送配置描述符(Configuration Descriptor),里面一般包含接口数量和供电配置信息;然后主机会根据配置描述符中的接口数量询问每一个接口的配置,这里就要发送端点描述符(Endpoint Descriptor)和上面提到的HID描述符(HID Descriptor);端点描述符一般包含端点的地址、最大包大小、传输间隔等信息。

        除了以上的描述符,主机还会请求字符串描述符(String Descriptor),这个一般就是描述厂商名字、产品名字等信息,每个字符串用一个描述符;这个是可选的,不发或发个空的也没啥问题。 

3.3 时钟校准控制器(CTC)

        在进入代码前还要再介绍一个外设——CTC。这个外设是专门用来校准IRC48M时钟的,因为内部时钟的精度是比较差的,而USB对时钟的要求是比较高的,因此如果我们使用IRC48M作为USB的时钟的话,就要使用CTC来实时校准IRC48M的精度。

        从上面可以看到, CTC的校准时钟可以选择GPIO时钟或外部低速时钟(LXTAL),一般会选择LXTAL。

        CTC的校准原理可以大概理解为:当REF同步脉冲信号出现时,时钟频率评估功能开始执行。如果REF同步脉冲信号出现在计数器向下计数的过程中,说明当前时钟频率比期望时钟频率(频率为48M)慢,需要增大TRIMVALUE值(时钟校准值)。如果REF同步脉冲信号出现在计数器向上计数的过程中,说明当前时钟频率比期望时钟频率快,需要减小TRIMVALUE值。

        状态寄存器中的CKOKIF、CKWARNIF、CKERR和REFMISS位反映了频率评估的状态。

3.4 代码

3.4.1 官方驱动移植

        官方例程里面已经基本上写好了大体的框架了,我们可以基于官方的代码进行修改,先导入一些必须的文件,在路径GD32F4xx_Firmware_Library_V3.2.0\Firmware\GD32F4xx_usb_library下面,全部导入的文件如下。

        导入的头文件路径参考如下。

        在全局宏定义里面加上USE_USB_FS。

3.4.2 初始化

        自己创建.c和.h文件编写业务代码。

static void hid_keyboard_bsp_init(void)
{/* 初始化GPIO */rcu_periph_clock_enable(RCU_GPIOA);gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_0);/* 初始化EXTI */rcu_periph_clock_enable(RCU_SYSCFG);syscfg_exti_line_config(EXTI_SOURCE_GPIOA, EXTI_SOURCE_PIN0);nvic_irq_enable(EXTI0_IRQn, 1, 0);exti_init(EXTI_0, EXTI_INTERRUPT, EXTI_TRIG_FALLING);exti_interrupt_enable(EXTI_0);/* 初始化USB */rcu_osci_on(RCU_IRC48M);  // 使能IRC48M时钟while(ERROR == rcu_osci_stab_wait(RCU_IRC48M));  // 等待时钟稳定/* 初始化外部低速时钟 */rcu_periph_clock_enable(RCU_PMU);pmu_backup_write_enable();rcu_osci_on(RCU_LXTAL);while(ERROR == rcu_osci_stab_wait(RCU_LXTAL));rcu_ckout0_config(RCU_CKOUT0SRC_LXTAL, RCU_CKOUT0_DIV1);  // 使能时钟输出,1分频/* 初始化CTC外设 */rcu_periph_clock_enable(RCU_CTC);ctc_refsource_prescaler_config(CTC_REFSOURCE_PSC_OFF);  // 不使用预分频ctc_refsource_signal_select(CTC_REFSOURCE_LXTAL);  // 校准源使用外部低速时钟ctc_refsource_polarity_config(CTC_REFSOURCE_POLARITY_RISING);  // 上升沿启动新一轮校准ctc_hardware_trim_mode_config(CTC_HARDWARE_TRIM_MODE_ENABLE);  // 使能硬件校准ctc_counter_reload_value_config(0x05B8);  // 1464 * 32.768kHz ≈ 48MHzctc_clock_limit_value_config(0x0002);  // 校准精度,±2个参考时钟周期ctc_counter_enable();  // 使能CTCwhile (ctc_flag_get(CTC_FLAG_CKOK) == RESET);  // 等待校准完成rcu_ck48m_clock_config(RCU_CK48MSRC_IRC48M);  // 选择IRC48M时钟为USB时钟rcu_periph_clock_enable(RCU_USBFS);  // 使能USB时钟/* 初始化USB管脚 */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);nvic_irq_enable(USBFS_IRQn, 2, 0);
}

        初始化的内容较多。首先就是初始化用户按键,随便选一个初始化GPIO和EXTI。接着使能IRC48M时钟,初始化CTC外设,这个比较重要。

        CTC我使用LXTAL,即外部低速晶振作为校准源,因此还需要初始化LXTAL;LXTAL部分需要使能PMU的时钟和使能backup域写,因为LXTAL是工作在Vbat域的。CTC的reload和limit值是关键,reload值是用来确定最终校准的时钟频率的,reload值×32.768kHz应该要尽可能等于48MHz,即USB的工作频率;limit值是确定校准的精度的,当测量出的时钟超过±limit值个参考时钟,CTC就认为时钟不稳定,会进行时钟校准。

        最后就是使能USBFS的时钟,和初始化USB的GPIO和中断,USB的中断优先级不要设得太高(不要高于延时的中断优先级),因为USB中断里面是会调延时函数的,如果USB中断优先级太高,延时中断就没办法处理了。

        既然讲到了延时,USB驱动需要移植2个延时函数。

void usb_udelay(const uint32_t usec)
{delay_us(usec);
}void usb_mdelay(const uint32_t msec)
{delay_ms(msec);
}

        同时需要移植USBFS的中断,直接调官方驱动的函数即可,hid_keyboard是一个自己定义的一个全局变量。

void USBFS_IRQHandler(void)
{extern usb_core_driver hid_keyboard;usbd_isr(&hid_keyboard);
}

3.4.3 业务功能部分

        业务部分就是简单写一个按键的处理,配合USB驱动的函数。

void hid_keyboard_process(usb_dev *udev)
{if (send_flag) {standard_hid_handler *hid = (standard_hid_handler *)udev->dev.class_data[USBD_HID_INTERFACE];if (hid->prev_transfer_complete) {/* 发送按键A */hid->data[2] = 0x04U;hid_report_send(udev, hid->data, HID_IN_PACKET);printf("send key\r\n");}send_flag = 0;}
}void EXTI0_IRQHandler(void)
{exti_interrupt_flag_clear(EXTI_0);send_flag = 1;
}

        HID键盘的数据包是固定8字节的,具体的定义可以看USB官方文档学习,这里只需要知道从第3个字节开始填键值即可,一个键值一字节。每个按键的键值是多少也是要看官方文档,字母A的键值就是4。调hid_report_send就可以发数据给主机了。

3.4.4 主函数

        usb_init函数可以帮我们完成所有的初始化工作,初始化后等待枚举成功才会进业务循环。

usb_core_driver hid_keyboard;int main(void)
{NVIC_SetPriorityGrouping(NVIC_PRIGROUP_PRE4_SUB0);debug_init();printf("hid keyboard demo\r\n");hid_keyboard_init();usbd_init(&hid_keyboard, USB_CORE_ENUM_FS, &hid_desc, &usbd_hid_cb);printf("usb init done\r\n");/* 等待USB枚举成功 */while (USBD_CONFIGURED != hid_keyboard.dev.cur_status);printf("usb enumation\r\n");while (1) {hid_keyboard_process(&hid_keyboard);}
}

3.5 运行测试

        烧录代码后用USB线连接开发板和电脑,在设备管理器里面就能看到多了一个HID键盘设备。

        按下我们设置的按键, 在文本框里面就会打出对应的字母。

这篇关于【GD32】从零开始学GD32单片机 | USB通用串行总线接口+HID键盘例程(GD32F470ZGT6)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1125298

相关文章

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机

键盘快捷键:提高工作效率与电脑操作的利器

键盘快捷键:提高工作效率与电脑操作的利器 在数字化时代,键盘快捷键成为了提高工作效率和优化电脑操作的重要工具。无论是日常办公、图像编辑、编程开发,还是游戏娱乐,掌握键盘快捷键都能带来极大的便利。本文将详细介绍键盘快捷键的概念、重要性、以及在不同应用场景中的具体应用。 什么是键盘快捷键? 键盘快捷键,也称为热键或快捷键,是指通过按下键盘上的一组键来完成特定命令或操作的方式。这些快捷键通常涉及同

Java 后端接口入参 - 联合前端VUE 使用AES完成入参出参加密解密

加密效果: 解密后的数据就是正常数据: 后端:使用的是spring-cloud框架,在gateway模块进行操作 <dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.0-jre</version></dependency> 编写一个AES加密

单片机毕业设计基于单片机的智能门禁系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍程序代码部分参考 设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订

生信代码入门:从零开始掌握生物信息学编程技能

少走弯路,高效分析;了解生信云,访问 【生信圆桌x生信专用云服务器】 : www.tebteb.cc 介绍 生物信息学是一个高度跨学科的领域,结合了生物学、计算机科学和统计学。随着高通量测序技术的发展,海量的生物数据需要通过编程来进行处理和分析。因此,掌握生信编程技能,成为每一个生物信息学研究者的必备能力。 生信代码入门,旨在帮助初学者从零开始学习生物信息学中的编程基础。通过学习常用

java线程深度解析(一)——java new 接口?匿名内部类给你答案

http://blog.csdn.net/daybreak1209/article/details/51305477 一、内部类 1、内部类初识 一般,一个类里主要包含类的方法和属性,但在Java中还提出在类中继续定义类(内部类)的概念。 内部类的定义:类的内部定义类 先来看一个实例 [html]  view plain copy pu

模拟实现vector中的常见接口

insert void insert(iterator pos, const T& x){if (_finish == _endofstorage){int n = pos - _start;size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;reserve(newcapacity);pos = _start + n;//防止迭代

京东物流查询|开发者调用API接口实现

快递聚合查询的优势 1、高效整合多种快递信息。2、实时动态更新。3、自动化管理流程。 聚合国内外1500家快递公司的物流信息查询服务,使用API接口查询京东物流的便捷步骤,首先选择专业的数据平台的快递API接口:物流快递查询API接口-单号查询API - 探数数据 以下示例是参考的示例代码: import requestsurl = "http://api.tanshuapi.com/a

BIRT--商业智能和报表工具,从零开始

1.简介 BIRT (Business Intelligence and Reporting Tools), 是为 Web 应用程序开发的基于 Eclipse 的开源报表系统,特别之处在于它是以 Java 和 JavaEE 为基础。BIRT 有两个主要组件:基于 Eclipse 的报表设计器,以及部署到应用服务器上的运行时组件。 2.下载 官网下载网址:http://download.ec

j2EE通用jar包的作用

原文:http://blog.sina.com.cn/s/blog_610901710101kx37.html IKIKAnalyzer3.2.8.jar // 分词器 ant-junit4.jar // ant junit antlr-2.7.6.jar // 没有此包,hibernate不会执行hql语句。并且会报NoClassDefFoundError: antlr