【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键盘设备。

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

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

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

相关文章

业务资源管理模式语言02

图1 模式间的依赖关系 第一节:最开始,关注应用中包括的资源。首先,必须标识资源(1),下一步,检查资源限定(2),同时量化资源(3) 模式1…

c#笔记5 详解事件的内置类型EventHandler、windows事件在winform中的运用

为什么要研究这一问题? 事件和委托可以说是息息相关。 前面先解释了什么是委托,怎么定义一个委托以及怎么使用匿名方法来内联地新建委托。 事实上事件这一机制在c#的程序开发中展很重要的地位,尤其是接触了winform软件开发的同学们应该都知…

Unity 动态光照贴图,加载后显示变暗或者变白问题 ReflectionProbe的使用

动态加载光照贴图代码,可参考这个帖子 Unity 预制动态绑定光照贴图遇到变白问题_unity urp 动态加载光照信息 变黑-CSDN博客 这次遇到的问题是,在编辑器下光照贴图能正常显示,打出apk后光照贴图加载后变黑的问题 以下4张图代表4种状态&…

opencascade 重叠曲线设置优先显示

‌OpenCASCADE重叠曲线显示设置‌ 当出现重叠曲线时,往往需要设置 优先显示的对象 关键点 SetDisplayPriority SetLayer

磁性齿轮箱市场报告:前三大厂商占有大约79.0%的市场份额

磁性齿轮箱是一种用于扭矩和速度转换的非接触式机构。它们无磨损、无摩擦、无疲劳。它们不需要润滑剂,并且可以针对其他机械特性(如刚度或阻尼)进行定制。 一、全球磁性齿轮箱行业现状与洞察 据 QYResearch 调研团队最新发布的“全球磁性齿轮…

10分钟了解OPPO中间件容器化实践

背景 OPPO是一家全球化的科技公司,随着公司的快速发展,业务方向越来越多,对中间件的依赖也越来越紧密,中间件的集群的数量成倍数增长,在中间件的部署,使用,以及运维出现各种问题。 1.中间件与业…

桥接与NET

仔细看看下面两幅图 net模式,就是在你的Windows电脑(假设叫A电脑)的网络基础上,再生成一个子网络,ip的前两位默认就是192.168,然后第三位是随机,第四位是自己可以手动设置的。使用这种模式唯一的…

设计模式结构型模式之代理模式

结构型模式之代理模式 一、概念和使用场景1、概念2、核心思想3、java实现代理模式的方式4、使用场景 二、示例讲解1. 静态代理2. 动态代理 三、总结1、使用规则2、代理模式的优点包括:3、代理模式的缺点包括: 一、概念和使用场景 1、概念 代理模式是一…

HUD杂散光环境模拟测试设备

概述 HUD(Head-Up Display)杂散光环境模拟测试设备是用于模拟飞行器在实际运行过程中可能遇到的多种光照环境的系统。它主要用于测试和验证HUD显示系统的性能,确保其能在各种光线条件下清晰、准确地显示信息,从而保障飞行员在复杂…

【大模型理论篇】通用大模型架构分类及技术统一化

1. 背景 国内的 “百模大战” 以及开源大模型的各类评测榜单令人眼花缭乱,极易让人陷入迷茫。面对如此众多的大模型,我们该如何审视和选择呢?本文将从大模型架构的角度,对常见的开源大模型架构进行汇总与分析。资料来源于公开…

2024全国大学生数学建模国赛,成员如何分工协作?

文末获取2024国赛数学建模思路代码,9.5开赛后第一时间更新 大家知道,数学建模竞赛是需要一个团队的三个人在三天或四天的时间内,完成模型建立,编程实现和论文写作的任务,对许多第一次参加建模或者建模经验比较欠缺的团…

Android 使用原生相机Camera在预览界面进行识别二维码或者图片处理

1 项目需求 最近项目中有个需求:使用原生相机在预览界面进行识别二维码和图片处理。其实这个需求不是很难,难在对预览画面的处理过程。 自己针对这个需求写了一个工具类,便于后续进行复盘,同时也分享给有类似需求的伙伴们。 2 遇到的问题 2.1 二维码识别成功率低 使用…

由浅入深学习 C 语言:Hello World【提高篇】

目录 引言 1. Hello World 程序代码 2. C 语言角度分析 Hello World 程序 2.1. 程序功能分析 2.2 指针 2.3 常量指针 2.4 指针常量 3. 反汇编角度分析 Hello World 程序 3.1 栈 3.2 函数用栈传递参数 3.3 函数调用栈 3.4 函数栈帧 3.5 相关寄存器 3.6 相关汇编指令…

优化学习管理:Moodle和ONLYOFFICE文档编辑器的完美结合

目录 前言 一、什么是 Moodle 1、简单快速插入表单字段 3、免费表单模板库 4、开启无缝协作 三、在Moodle中集成ONLYOFFICE文档 四、在Moodle安装使用ONLYOFFICE 1、下载安装 2、配置服务器 3、在Moodle中使用ONLYOFFICE 文档活动 五、未来展望 写在最后 前言 在当今教育科技飞…

JVM垃圾回收算法:标记-清除算法 、复制算法、 标记-整理算法、 分代收集算法

文章目录 引言I 标记回收算法(Mark-Sweep)算法不足II 复制算法(Copying)III 标记整理算法(Mark-Compact)IV 分代收集(以上三种算法的集合体)内存划分新生代算法:Minor GC老年代算法V 查看JVM堆分配引言 垃圾回收(Garbage Collection,GC) Java支持内存动态分配、…

Flask+LayUI开发手记(六):树型表格的增删改查

树型表格的增删改查功能与数据表格的是完全一致,就是调用layui-form表单组件实现数据输入再提交,比较大的区别是树型节点的编辑,都需要有上级节点的输入,而这个上级节点的展示,必须是以树型方式展示出来。当然&#xf…

语音控制开关的语音识别ic芯片方案

语音控制开关是一种基于语音识别技术的设备,它通过内置的语音识别芯片,将用户的语音指令转化为电信号,从而实现对设备的控制。例如在智能家居设备上的应用,通常需要连接到家庭的Wi-Fi网络上,以便与智能手机或智能音箱等…

golang RSA 解密前端jsencrypt发送的数据时异常 crypto/rsa: decryption error 解决方法

golang中 RSA解密前端(jsencrypt)发来的密文后出现 "crypto/rsa: decryption error" , 这个问题首先需要确认你的私匙和公匙是否匹配, 如果匹配 那检查入参数据类型, 前端发送来的rsa加密后的数据一般都是…

bbr 随机 phase 的麻烦与 inflight 守恒算法的动机

bbr 有个要点,要把 probebw 的 phase 错开: static void bbr_reset_probe_bw_mode(struct sock *sk) {struct bbr *bbr inet_csk_ca(sk);bbr->mode BBR_PROBE_BW;bbr->cycle_idx CYCLE_LEN - 1 - prandom_u32_max(bbr_cycle_rand);bbr_advance…