【STM32 USB】USB CDC类

简介

USB CDC(communication device class)类是usb2.0标准下的一个子类,定义了通信相关设备的抽象集合。usb2.0标准下定义了很多子类,有音频类,CDC类,HID类,打印,大容量存储类,HUB和智能卡等等。

在这里插入图片描述

CDC类下根据应用场合,又分为多个子类,官方文档主要讲的是PSTN。PSTN(Public Switched Telephone Network)是一个与电信相关的子类,这里只是将其当作一个普通的通信设备使用,并没有使用它的一些电话特性。

使用USB中的CDC类来虚拟串口 Virtual COM Port (VCP)进行通讯是一种非常好用的方式,一方面对于上位机来说显示出来的就是一个串口,所有操作都还是对串口的操作;另一方面实际数据传输是基于USB的,数据传输速度得到大大提升。并且STM32 CDC VCP对于win10和较新版本的Linux来说是免驱的。

示例

使用STM32CubeIDE生成代码。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

将代码编译烧录后,我们可以看到多了一个串口设备。

在这里插入图片描述

其他说明

从USB版本来说目前STM32系列MCU可以认为都是USB2.0的。从硬件接口功能上来说STM32系列MCU的USB分为 USB_FS 、 USB_OTG_FS 、 USB_OTG_HS 三种。其中的FS指的是全速(Full Speed),HS指的是高速(High Speed)。OTG指的是既可以作为Device(从设备)使用,也可以作为Host(主机)使用。

Full Speed 理论上速度为12Mbit/s,High Speed 理论上速度为480Mbit/s ,当然这都是理论速度,实际上通讯速度还依赖于所用通讯方式和设备性能。

对于STM32系列MCU而言,USB FS的使用只要使用 DM / D- 和 DP / D+ 这两个引脚就行了,最多也就加上ID、SOF、VBUS这三个引脚。而使用USB HS大多数还需要外接PHY芯片(比如USB3300),这样使用的引脚就多了,至少也要用到12个引脚。STM32系列MCU中目前只有STM32F723内置USB HS PHY功能,不需要外接PHY芯片。

我们在上面的示例中,是外接了PHY芯片的,所以选择配置的时候是用的USB_HS。

用户代码分析

上述配置生成的代码中,对于用户来说USB使用相关的代码都在 USB_DEVICE > App 中,这其中最重要的就是 usbd_cdc_if.c 文件,大多数时候我们只要改写这个文件就可以实现相关需求了,该文件主要结构与说明如下:

#include "usbd_cdc_if.h"// 数据收发缓存,这部分也可以完全由用户自行定义
uint8_t UserRxBufferFS[APP_RX_DATA_SIZE]; // 接收缓存
uint8_t UserTxBufferFS[APP_TX_DATA_SIZE]; // 发送缓存extern USBD_HandleTypeDef hUsbDeviceFS;// 初始化USB_CDC
static int8_t CDC_Init_FS(void)
{USBD_CDC_SetTxBuffer(&hUsbDeviceFS, UserTxBufferFS, 0); // 设置发送缓存USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS); // 设置接收缓存
}// 反初始化USB_CDC
static int8_t CDC_DeInit_FS(void){}// 来自主机的请求处理
// cmd: 命令代码
// pbuf & length: 请求数据指针与长度
static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* pbuf, uint16_t length)
{switch(cmd){/***********************************************************************************************//* Line Coding Structure                                                                       *//*---------------------------------------------------------------------------------------------*//* Offset | Field       | Size | Description                                                   *//* 0      | dwDTERate   |   4  | Data terminal rate, in bits per second                        *//* 4      | bCharFormat |   1  | Stop bits: 0 - 1 Stop bit; 1 - 1.5 Stop bits; 2 - 2 Stop bits *//* 5      | bParityType |   1  | Parity: 0 - None; 1 - Odd; 2 - Even; 3 - Mark; 4 - Space      *//* 6      | bDataBits   |   1  | Data bits (5, 6, 7, 8 or 16).                                 *//***********************************************************************************************/case CDC_SET_LINE_CODING: break; // 主机设置串口参数}
}// 接收回调函数
// Buf & Len: 当前收到这一包数据指针与长度
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]); // 重新设置接收缓存// 注意默认情况下上面一行代码相当于 USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS);USBD_CDC_ReceivePacket(&hUsbDeviceFS); // 重新启动数据接收
}// 数据发送函数
// Buf & Len: 要发送的数据指针与长度
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
{USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;if (hcdc->TxState != 0){return USBD_BUSY; // 如果当前USB繁忙则返回USBD_BUSY}USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len); // 设置要发送的数据result = USBD_CDC_TransmitPacket(&hUsbDeviceFS); // 发送数据
}// 发送完成回调函数
// Buf & Len: 所送的数据指针与长度
static int8_t CDC_TransmitCplt_FS(uint8_t *Buf, uint32_t *Len, uint8_t epnum){}

上面代码中最常处理的只有下面四个函数:
CDC_Control_FS() 来自主机请求的回调函数
CDC_Receive_FS() 接收数据回调函数;
CDC_Transmit_FS() 用来发送数据;
CDC_TransmitCplt_FS() 发送完成回调函数;

发送

uint8_t  CDC_Transmit_FS ( uint8_t* Buf,  uint16_t Len );
static int8_t CDC_TransmitCplt_HS(uint8_t *Buf, uint32_t *Len, uint8_t epnum)

我们需要发送数据的时候,需要调用CDC_Transmit_FS函数,该函数的Len参数好像并不会限制在2048。然后我们在发生完成函数中修改如下:

static int8_t CDC_TransmitCplt_HS(uint8_t *Buf, uint32_t *Len, uint8_t epnum)
{uint8_t result = USBD_OK;/* USER CODE BEGIN 14 */printf("CDC_TransmitCplt_HS Len=%d\r\n", *Len);UNUSED(Buf);UNUSED(Len);UNUSED(epnum);/* USER CODE END 14 */return result;
}

在这里插入图片描述

我们可以看到,当一次性发送的数据包大于512的时候,发送函数内部会自动分包,直至发完,就会调用CDC_TransmitCplt_HS函数。接收端好像也会自动组包然后一次性收完?

还有一点需要注意,在发送函数内部,有一个设备状态判断,这意味着如果当前正在发送一包数据,然后你又调用该函数发送一包数据,是会发送失败的。可以理解为发送函数它是一个异步函数,数据真正发完的时候会调用CDC_TransmitCplt_HS函数。所以,我们在发送下一包数据的时候,需要确保上一包数据已经发送完成了,一般可以使用一个信号量进行阻塞之类的。

关于发送函数的总结:

  • 发送函数的数据长度可以大于一包USB数据的长度,函数内部会自动分包。
  • 发送函数是一个异步函数,调用CDC_TransmitCplt_HS函数的时候,数据才真正发完。

接收

当USB CDC接收到来自USB主机的数据时,触发中断进入中断函数,继而自动调用接收回调函数:

int8_t  CDC_Receive_FS (uint8_t* Buf, uint32_t *Len);  
  • uint8_t* Buf: 指向接收缓冲区的指针,即数据缓存的地址。
  • uint16_t* Len: 当前数据包的字节数。

我们将代码改为如下

static int8_t CDC_Receive_HS(uint8_t* Buf, uint32_t *Len)
{/* USER CODE BEGIN 11 */printf("CDC_Receive_HS Len=%d\r\n",*Len);USBD_CDC_SetRxBuffer(&hUsbDeviceHS, &Buf[0]);USBD_CDC_ReceivePacket(&hUsbDeviceHS);return (USBD_OK);/* USER CODE END 11 */
}

然后使用串口助手往底板发送数据的结果如下:

在这里插入图片描述

可以看到,我们串口一次性发送了6868个字节的数据,在底板是分多次接收的。这是为什么呢?这是因为我们在配置USB CDC的时候,USB协议已经规定了一个USB包的数据最大就是512字节,这并不意味着超过512字节的数据帧就不能够发送给单片机,从上例就可以看出,USB协议会自动分包,然后单片机会有多次接收,只是一次接收不能接收所有完整的数据帧。所以,如果有大于512的数据帧时,我们可以在接收端手动组包,将多次接收的一个数据包组成完成的数据帧。组包的机制可以是定义帧头帧尾或者定义包序号,这里就不展开了。

还有一点示例可能看不出来,就是接收函数其实就是一个中断服务函数。所以,我们在该函数中的处理时间应该尽可能的短,因为在接收函数中进行耗时操作时,可能正好有一包数据需要接收,这时可能就会漏收。一般建议在该函数中接收数据时,将数据转存到其他的队列或者缓存空间。

关于接收函数的总结:

  • 如果上位机一次发送的数据帧大于USB最大包长度,那么在接收端需要注意组包。
  • 在接收函数中尽量不要进行耗时的操作,如需处理数据可以先转存到其他地方。

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

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

相关文章

如何修改Windows系统Ollama模型存储位置

默认情况下,Ollama 模型会存储在 C 盘用户目录下的 .ollama/models 文件夹中,这会占用大量 C 盘空间,增加C盘“爆红”的几率。所以,我们就需要修改Ollama的模型存储位置 Ollama提供了一个环境变量参数可以修改Ollama的默认存在位…

DeepSeek掘金——VSCode 接入DeepSeek V3大模型,附使用说明

VSCode 接入DeepSeek V3大模型,附使用说明 由于近期 DeepSeek 使用人数激增,服务器压力较大,官网已 暂停充值入口 ,且接口响应也开始不稳定,建议使用第三方部署的 DeepSeek,如 硅基流动 或者使用其他模型/插件,如 豆包免费AI插件 MarsCode、阿里免费AI插件 TONGYI Lin…

【语音科学计算器】当前汇率

JSON_MARKER_HORN{“base”:“USD”,“rates”:{“EUR”:0.9758,“JPY”:157.68,“GBP”:0.8190,“CNY”:7.3327,“HKD”:7.7872,“AUD”:1.6260,“CAD”:1.4422,“CHF”:0.9157,“SGD”:1.3714,“KRW”:1473.05,“NZD”:1.7992,“THB”:34.54,“MYR”:4.4930,“PHP”:57.32,“…

Codes 开源免费研发项目管理平台 2025年第一个大版本3.0.0 版本发布及创新的轻IPD实现

Codes 简介 Codes 是国内首款重新定义 SaaS 模式的开源项目管理平台,支持云端认证、本地部署、全部功能开放,并且对 30 人以下团队免费。它通过创新的方式简化研发协同工作,使敏捷开发更易于实施。并提供低成本的敏捷开发解决方案&#xff0…

uniapp 网络请求封装(uni.request 与 uView-Plus)

一、背景 在开发项目中,需要经常与后端服务器进行交互;为了提高开发效率和代码维护性,以及降低重复性代码,便对网络请求进行封装统一管理。 二、创建环境文件 2.1、根目录新建utils文件夹,utils文件夹内新建env.js文…

ESP32-S3 实战指南:BOOT-KEY 按键驱动开发全解析

一、基础知识 本篇我们使用 BOOT 按键来学习一下 GPIO 功能,首先补充一下相关术语介绍。 1、GPIO(General Purpose Input/Output) GPIO 是微控制器上的通用引脚,既可以作为输入(读取外部信号)&#xff0…

初学者如何设置以及使用富文本编辑器[eclipse版]

手把手教你设置富文本编辑器 参考来源:UEditor Docs 初学者按我的步骤来就可以啦 一、设置ueditor编辑器 1.提取文件[文章最底部有链接提取方式] 2.解压文件并放到自己项目中,在WebContent目录下: 3. 修改jar包位置路径 到--> 注意&a…

25轻化工程研究生复试面试问题汇总 轻化工程专业知识问题很全! 轻化工程复试全流程攻略 轻化工程考研复试真题汇总

轻化工程复试心里没谱?学姐带你玩转面试准备! 是不是总觉得老师会问些刁钻问题?别焦虑!其实轻化工程复试套路就那些,看完这篇攻略直接掌握复试通关密码!文中有重点面试题可直接背~ 目录 一、这些行为赶紧避…

企业数据集成:实现高效调拨出库自动化

调拨出库对接调出单-v:旺店通企业奇门数据集成到用友BIP 在企业信息化管理中,数据的高效流转和准确对接是实现业务流程自动化的关键。本文将分享一个实际案例,展示如何通过轻易云数据集成平台,将旺店通企业奇门的数据无缝集成到用…

Java高级开发所具知识技能

以下是Java高级开发整理的知识技能,其中涵盖核心技术、框架、分布式架构、性能优化等关键领域: 一、Java核心进阶 JVM深度理解 内存模型(堆、栈、方法区)垃圾回收算法(CMS、G1、ZGC)类加载机制与字节码增强JVM调优工具(jstat、jmap、VisualVM、Arthas)并发编程 线程池(…

【SQL】多表查询案例

📢本章节主要学习使用SQL多表查询的案例,多表查询基础概念 请点击此处。 🎄数据准备 首先我们创建一个新的表也就是薪资等级表,其余两个表(员工表和薪资表)在多表查询章节中已经创建。然后我么根据这三个表完成下面的12个需求。 create tab…

PyTorch v2.6 Overview

PyTorch v2.6 Overview Python APILibraries PyTorch 是一个优化的张量库,用于使用 GPU 和 CPU 进行深度学习。 Python API 序号API名称解释1torchPyTorch 核心库(中文:火炬)PyTorch 的核心库,提供了张量操作、自动求导等基础功能。2torch.nn神经网络模…

如何调整CAN位宽容忍度?

CAN位宽容忍度是指在控制器局域网络(CAN, Controller Area Network)中允许时钟同步的误差范围。这是CAN网络正常通信时的关键因素之一,因为CAN协议依赖位同步来确保多个节点在总线上正确解码数据。CAN位宽容忍度确保节点之间由于时钟偏差或抖…

Django-Vue 学习-VUE

主组件中有多个Vue组件 是指在Vue.js框架中,主组件是一个父组件,它包含了多个子组件(Vue组件)。这种组件嵌套的方式可以用于构建复杂的前端应用程序,通过拆分功能和视图,使代码更加模块化、可复用和易于维…

怎么学习调试ISP的参数

摄像头的 **Sensor 获取的 RAW 数据** 是未经处理的原始图像数据,通常需要经过 **ISP(Image Signal Processor,图像信号处理器)** 的处理,才能生成可用的图像或视频。ISP 的作用是对 RAW 数据进行一系列图像处理操作&a…

万字长文解析:深入理解服务端渲染(SSR)架构与全栈实践指南

一、SSR核心原理深度剖析 1.1 技术定义与演进历程 服务端渲染(Server-Side Rendering)指在服务器端完成页面DOM构建的技术方案。其发展历程可分为三个阶段: 阶段时期典型技术传统SSR2000-2010JSP/PHP现代SSR2015-2020Next.js/Nuxt.js混合渲…

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_array_push

ngx_array_push 声明在 src\core\ngx_array.h void *ngx_array_push(ngx_array_t *a); 实现在 src\core\ngx_array.c void * ngx_array_push(ngx_array_t *a) {void *elt, *new;size_t size;ngx_pool_t *p;if (a->nelts a->nalloc) {/* the array is full…

python用 PythonNet 从 Python 调用 WPF 类库 UI 用XAML

pythonnet 是pythonhe.net通用的神器不多介绍了. 这次这基本上跟python没有关系了. 和winform一样先导包 import clr clr.AddReference("PresentationFramework.Classic, Version3.0.0.0, Cultureneutral, PublicKeyToken31bf3856ad364e35") clr.AddReference(&…

MySql数据库运维学习笔记

数据库运维常识 DQL、DML、DCL 和 DDL 是 SQL(结构化查询语言)中的四个重要类别,它们分别用于不同类型的数据库操作,下面为你简单明了地解释这四类语句: 1. DQL(数据查询语言,Data Query Langu…

如何为自己的 PDF 文件添加密码?在线加密 PDF 文件其实更简单

随着信息泄露和数据安全问题的日益突出,保护敏感信息变得尤为重要。加密 PDF 文件是一种有效的手段,可以确保只有授权用户才能访问或修改文档内容。本文将详细介绍如何使用 CleverPDF 在线工具为你的 PDF 文件添加密码保护,确保其安全性。 为…