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

news2024/9/20 9:22:04

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 ≈ 48MHz
    ctc_clock_limit_value_config(0x0002);  // 校准精度,±2个参考时钟周期
    ctc_counter_enable();  // 使能CTC
	while (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键盘设备。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2092115.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

04:布局规划

1.切换到丝印层 2.用2D线对模块区域划分

keil5烧录后不自动复位和Flash Download failed - “Cotex-M3“报错解决

目录 项目场景: 复位问题描述 复位原因分析: 复位解决方案: 下载错误问题描述 下载错误原因分析: 下载错误解决方案: 总结 项目场景: keil5编译stm32例程在烧录时候遇到的一些问题 复位问题描述 1. 使…

全面解读LSC局域网屏幕监控软件:功能、优势与应用场景一网打尽!

在信息化高速发展的今天,企业管理的效率和精准度成为决定竞争力的关键因素。 LSC局域网屏幕监控软件(LAN Screen Capture),作为安企神推出的一款专为现代企业量身打造的超级局域网监控管理软件,以其强大的功能和灵活的…

MAC环境导出项目的目录结构

一、安装Homebrew包管理工具 /bin/bash -c "$(curl -fsSL https://gitee.com/ineo6/homebrew-install/raw/master/install.sh)" 官网网址:https://brew.idayer.com/ 二、用brew包管理工具安装tree brew install tree 三、打开终端,导出项目…

怎么用AI做视频总结?

利用AI工具批量生成影视短剧推广https://docs.qq.com/doc/DYnl6d0FLdHp0V2ll 搞个插件就可以了。 我只能说AI的终极目的就是为了视频服务的,语音(配音)、视频脚本(文案)、绘图(画面)、设计&…

数据结构:单链表逆置的相关问题

1.思路:这里主要是用到头插法的思想进行单链表的一个逆置 2.知识点回顾: (1)头插法 [1]什么叫头插法:新增节点在头节点后面(下图为单链表结构) [2&#xf…

Leetcode 257-二叉树的所有路径

给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点的节点。 题解 递归回溯 遇到叶节点返回 每层的做法,list加上当前节点的string值 本题解将res作为全局变量,作为局部变量写法也…

图像边缘检测Canny

一、Canny边缘检测原理 边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。 Canny边缘检测算法是由4步构成:噪声去除、计算图像梯度、非极大值抑制、滞后阈值 1、噪声去除:由于边缘检测很容易受到…

Vulnhub靶场 | DC系列 - DC6

文章目录 DC-6环境搭建渗透测试 DC-6 环境搭建 靶机镜像下载地址:https://vulnhub.com/entry/dc-6,315/需要将靶机和 kali 攻击机放在同一个局域网里;本实验kali 的 IP 地址:192.168.10.146。 渗透测试 使用 nmap 扫描 192.168.10.0/24 网…

【报错已解决】`ValueError: Expected 2D array, got 1D array instead`的

🎬 鸽芷咕:个人主页 🔥 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 引言: 在使用机器学习库(如scikit-learn)时,开发者可能会遇到ValueError: Expec…

Qt/C++地址转坐标/坐标转地址/逆地址解析/支持百度高德腾讯和天地图

一、前言说明 地址和经纬度坐标转换的功能必须在线使用,一般用在导航需求上,比如用户输入起点地址和终点地址,查询路线后,显示对应的路线,而实际上各大地图厂家默认支持的是给定经纬度坐标来查询(百度地图…

光纤FPV无人机技术详解

1. 技术基础与原理 光纤FPV(First Person View,第一人称视角)无人机技术,是将光纤通信技术与无人机技术相结合的一项创新技术。该技术通过光纤作为高速、低延迟的数据传输媒介,实现了无人机拍摄的高清视频信号实时回传…

P0.9/P1.25全倒装共阴节能COB超微小间距LED显示屏已抢占C位

COB(Chip on Board)技术最早发源于上世纪60年代,是将LED芯片直接封装在PCB电路板上,并用特种树脂做整体覆盖。COB实现“点” 光源到“面” 光源的转换。点间距有P0.3、P0.4、P0.5、P0.6、P0.7、P0.9、P1.25、P1.538、P1.5625、P1.…

带有WebUI的cron替代品Dagu

什么是 Dagu ? Dagu 是一个强大的 Cron 替代品,它带有一个 Web UI。它允许你将命令之间的依赖关系定义为有向无环图(DAG),使用声明式的 YAML 格式。Dagu 的设计易于使用、自包含且无需编码,非常适合小型项目。 安装 在群晖上以 D…

科技与文化的完美碰撞 德施曼玄武•紫禁城K80亮相成都车展

8月30日-9月8日,第二十七届成都国际汽车展览会将在中国西部国际博览城举行,德施曼将与海尔、美的、松下等知名品牌携旗下重磅产品集中参展。大会同期“京东MALL智享家生活”活动也将开启,行业首个与紫禁城IP联名的智能锁产品德施曼玄武•紫禁…

52基于SpringBoot+Vue+uniapp的旅游管理系统的的详细设计和实现(源码+lw+部署文档+讲解等)

文章目录 前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus 系统测试系统测试目的系统功能测试系统测试结论 为什么选择我代码参考数据库参考源码获取源码获取 前言 🌞博主介绍 :✌全网粉丝15W,CSDN特邀作者、21…

【初阶C++篇】~ C++入门

C入门基础 前言8月语言排行 一 C书籍推荐二 C的第一个程序​三命名空间 namespace命名空间的使用 四C输入 输出五缺省参数六函数重载(就是函数可以同名)七引用引用和指针的关系 八 inline内联 前言 学完数据结构,那么C也就随之而来&#xff…

数据分析处理库(pandas)

目录 数据预处理 数据读取 DataFrame结构 数据索引 创建DataFrame Series操作 数据分析 统计分析 pivot数据透视表 groupby操作 常用函数操作 Merge操作 排序操作 缺失值处理 apply自定义函数 时间操作 绘图操作 大数据处理技巧 数值类型转换 属性类型转换…

FPGA学习笔记

FLASH: 掉电程序不丢失,属于ROM. 是的,Flash 存储器属于 ROM(只读存储器)的范畴,但它与传统的 ROM(如 PROM、EPROM)有一些区别。 Flash 与 ROM 的关系 ROM(Read-Only Memory&#…

企业级Mysql 集群技术部署

目录 1.1部署mysql 1.1.1 安装依赖性: 1.1.2 下载并解压源码包 1.1.3 源码编译安装mysql 1.1.4 部署mysql 2.mysql的主从复制 2.1 配置masters 2.2配置slave 2.3 延迟复制 2.4 慢查询日志 2.5并行复制 2.6 原理刨析 2. 7架构缺陷 3.半同步模式 3.1半同…