STM32HAL----USB模拟串口(VCP)

2023-12-09 11:40
文章标签 模拟 串口 stm32hal usb vcp

本文主要是介绍STM32HAL----USB模拟串口(VCP),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

      想要实现的功能是,USB模拟串口收发数据。串口助手发送数据至MCU,MCU接收后返回给串口助手。

      当初是想用标准库做这个功能的。但是因为后来了解到STM32CubeMX这个软件,在尝试之后实在是感觉,太方便了。所以,并没有使用标准库,而是直接用STM32CubeMX生成HAL库的代码用了。

(1)先点New Project,然后输入自己的MCU型号

(2)配置引脚与外设

这里我用的是ST-LINK进行DeBug,Tim5提供系统延时节拍,PE5与PB5点亮LED。而SysTick,用在FreeRtos提供系统节拍。

(3)时钟树配置

(4)配置外设

这个页面可以对外设进行功能的设置,比如GPIO的输出类型或者引脚初始电平。在这里主要设置FreeRTOS,创建2个初始任务。其他的比如USB,默认就可以使用了。

(5)Poject Settings

这里注意两个地方,我使用的是MDK。所以IDE选项选择的是MDK-ARM V5。CodeGenerator选项卡下,将Generated files下的第一个选项打上勾,这样就会启用模块化编程,不同的外设封装不同的.c   .h文件。至于Project Name跟Project Location,

自行设置便可。

然后,点击STM32CubeMX主界面的Project,Generate Code。就能在我们指定的文件夹内直接生成工程文件。生成之后软件提示你打开项目,点击打开后,工程内分组如图:

 

因为使用了RTOS,所以编程主要围绕两个文件,“usbd_cdc_if.c”以及“freertos.c”

“usbd_cdc_if.h”添加一个USB管理结构体的定义,并将“usbd_cdc_if.c”中两个定义移到“usbd_cdc_if.h”

/* USER CODE BEGIN PRIVATE_DEFINES */
/* Define size for the receive and transmit buffer over CDC */
/* It's up to user to redefine and/or remove those define */
#define APP_RX_DATA_SIZE  1000
#define APP_TX_DATA_SIZE  1000typedef struct  
{  uint8_t   OutFlag;uint8_t   EFlag[2];uint8_t   SFlag;  uint16_t  ReLen; 	
}USB_Dev; 	 /* USER CODE END PRIVATE_DEFINES */
/* USER CODE BEGIN INCLUDE */

“usbd_cdc_if.c”声明USB管理结构体变量并赋值,且修改“CDC_Receive_FS”函数。

/* Private typedef -----------------------------------------------------------*/
USB_Dev  USB_S =
{0,{0x0D,0x0A},0,0,
};
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5); //开启接收指示灯//将已接收数据长度赋值给USB_S.ReLenUSB_S.ReLen += *Len;//判断是否有结束标志以及接收数据长度是否达到UserRxBufferFS长度上限if( USB_S.ReLen<APP_RX_DATA_SIZE && \UserRxBufferFS[USB_S.ReLen-2] != USB_S.EFlag[0] && \UserRxBufferFS[USB_S.ReLen-1] != USB_S.EFlag[1]) {//设置下一次接收数据的位置USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS + USB_S.ReLen);USBD_CDC_ReceivePacket(&hUsbDeviceFS);   //准备接收数据}else  //长度达到,或者检测到标志位,触发数据输出{USB_S.OutFlag = 1;}return (USBD_OK);
}

 

 

 

“freertos.c”中,添加头文件“usbd_cdc_if.h”并在对应的任务中添加对应功能:

/* LED_Toggle function */
void LED_Toggle(void const * argument)
{for(;;){HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);osDelay(500);}}extern USB_Dev  USB_S;
extern USBD_HandleTypeDef hUsbDeviceFS;
extern uint8_t UserRxBufferFS[APP_RX_DATA_SIZE];
/* USB_SendMess function */
void USB_SendMess(void const * argument)
{uint16_t timeout = 0xffff;uint8_t temp;for(;;){timeout = 0xffff;if(USB_S.OutFlag){temp = !(USB_S.ReLen%64);  //判断长度是否为64整数倍while( CDC_Transmit_FS(UserRxBufferFS, USB_S.ReLen - temp) != USBD_OK && timeout--);if(temp)  //当发送数据为64整数倍时,无法发送成功,故分成2次发送{while( CDC_Transmit_FS(UserRxBufferFS + USB_S.ReLen -1, temp) != USBD_OK && timeout--);}USB_S.ReLen = 0;USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS);USBD_CDC_ReceivePacket(&hUsbDeviceFS);USB_S.OutFlag = 0;	HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET); //接收指示灯关闭				}osDelay(1);}
}

 

启动文件:需将堆的容量调大,因为USB缓存使用的是堆空间。我将它调为4K:

Heap_Size      EQU     0x1000

 

至此,代码创建并修改完毕。

 

下载代码到板子,下载完毕后关闭重上电,然后打开串口助手对应的串口。波特率之类全都不需设置。

发送一篇长文章,MCU接收后返回给串口助手

现象如图:

 

 

 

写在最后

(1)MCU端接收数据问题。

        USB传输一次最多64字节。所以,如果想一次传输大量数据给MCU。需制定协议。如图的串口助手,在发送完毕数据之后,会补上2字节结束标志:0x0D,0x0A。MCU端可根据结束标志判断数据是否接收完毕。需注意的是,不同的串口助手协议不一定相同。有的是根据数据包长度判断是否接收完毕。

         

(2)连续输出的问题。

        1、需要轮询发送函数返回值是否是“USBD_OK”

        2、输出字符串,如果不去掉字符串最后的结束符。某些上位机软件,只能显示1次发送的数据,第二次

             发送的数据不能正常显示。(本博客使用的串口助手就是如此。。。。)  ,但使用过另外一款串口

             助手,并未发现此情况。    

        3、轮询返回值有可能导致程序卡死,因为主机若是没有接收MCU发送的数据,MCU会一直轮询

             直到主机接收完数据为止,解决办法是加个timeout--到while里面,到时间跳出循环。

(3)WINDOWS下不能识别的串口有黄色感叹号。

        这个有可能是堆设置不够大。我在F4下遇到了这问题,将堆空间“Heap_Size      EQU     0x200”

        设置为“Heap_Size      EQU     0x1000”,黄色感叹号消失

  (4)通讯速度问题

       参考“http://bbs.21ic.com/icview-811704-1-1.html”,附件中有测试速度的软件。

              串口助手当通讯速度40KB/S左右,接收不到数据。当然,如果是连续发送'0',串口助手上窗口不

       显示字符,是可以接收到数据的,但当速度超过500KB/S,依旧不能接收数据。所以,网上很多反映

       VCP速度只有几十KB/S的,估计是上位机软件问题。

              使用附件中的软件,可以成功测出速度。STM32CubeMX生成的程序,经测试发送到主机的速度可以

       达到1000KB/S以上。

      

(5)“CDC_Receive_FS”函数的解析

      这个函数的作用是两个,一是设置下一次接收数据的Buff,二是处理接收端点。

      这个函数是在MCU接收完数据之后才调用的,而不是进入这个函数才开始接收数据。比方MCU接收到64byte的数据,

      接收完成后进入这个函数,设置下一次接收数据的Buff。然后调用“USBD_CDC_ReceivePacket”处理接收端点。

      如果不调用“USBD_CDC_ReceivePacket”,是无法进行下一次的接收的,但发送是可以的。

(6)CDC_Transmit_FS 发送64整数倍字节数的数据出错

      当我调用“CDC_Transmit_FS”发送64字节的数据时,串口助手并不能接收到数据。

      网上有帖子解释了这种情况:http://bbs.21ic.com/icview-159300-1-1.html

 

      总的来说,USB的bulk协议以发送小于64字节或者是字长为0的数据包作为结束动作。

      发送64字节的包的时候,只需要末尾再发送个字长为0的包即可。

 

 

这篇关于STM32HAL----USB模拟串口(VCP)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

usaco 1.2 Transformations(模拟)

我的做法就是一个一个情况枚举出来 注意计算公式: ( 变换后的矩阵记为C) 顺时针旋转90°:C[i] [j]=A[n-j-1] [i] (旋转180°和270° 可以多转几个九十度来推) 对称:C[i] [n-j-1]=A[i] [j] 代码有点长 。。。 /*ID: who jayLANG: C++TASK: transform*/#include<

hdu4431麻将模拟

给13张牌。问增加哪些牌可以胡牌。 胡牌有以下几种情况: 1、一个对子 + 4组 3个相同的牌或者顺子。 2、7个不同的对子。 3、13幺 贪心的思想: 对于某张牌>=3个,先减去3个相同,再组合顺子。 import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.IOExcepti

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟)

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟) 题目描述 给定一个链表,链表中的每个节点代表一个整数。链表中的整数由 0 分隔开,表示不同的区间。链表的开始和结束节点的值都为 0。任务是将每两个相邻的 0 之间的所有节点合并成一个节点,新节点的值为原区间内所有节点值的和。合并后,需要移除所有的 0,并返回修改后的链表头节点。 思路分析 初始化:创建一个虚拟头节点

每日一题|牛客竞赛|四舍五入|字符串+贪心+模拟

每日一题|四舍五入 四舍五入 心有猛虎,细嗅蔷薇。你好朋友,这里是锅巴的C\C++学习笔记,常言道,不积跬步无以至千里,希望有朝一日我们积累的滴水可以击穿顽石。 四舍五入 题目: 牛牛发明了一种新的四舍五入应用于整数,对个位四舍五入,规则如下 12345->12350 12399->12400 输入描述: 输入一个整数n(0<=n<=109 ) 输出描述: 输出一个整数

【算法专场】模拟(下)

目录 前言 38. 外观数列 算法分析 算法思路 算法代码 1419. 数青蛙 算法分析 算法思路 算法代码  2671. 频率跟踪器 算法分析 算法思路 算法代码 前言 在前面我们已经讲解了什么是模拟算法,这篇主要是讲解在leetcode上遇到的一些模拟题目~ 38. 外观数列 算法分析 这道题其实就是要将连续且相同的字符替换成字符重复的次数+

模拟实现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;//防止迭代

PHP实现二叉树遍历(非递归方式,栈模拟实现)

二叉树定义是这样的:一棵非空的二叉树由根结点及左、右子树这三个基本部分组成,根据节点的访问位置不同有三种遍历方式: ① NLR:前序遍历(PreorderTraversal亦称(先序遍历)) ——访问结点的操作发生在遍历其左右子树之前。 ② LNR:中序遍历(InorderTraversal) ——访问结点的操作发生在遍历其左右子树之中(间)。 ③ LRN:后序遍历(PostorderT

1 模拟——67. 二进制求和

1 模拟 67. 二进制求和 给你两个二进制字符串 a 和 b ,以二进制字符串的形式返回它们的和。 示例 1:输入:a = "11", b = "1"输出:"100"示例 2:输入:a = "1010", b = "1011"输出:"10101" 算法设计 可以从低位到高位(从后向前)计算,用一个变量carry记录进位,如果有字符没处理完或者有进位,则循环处理。两个字符串对

Usb Audio Device Descriptor(10) Hid Device

对于 Standard Interface Descriptor, 当 bInterfaceClass=0x03时,即为HID设备。Standard Interface Descriptor如下 struct usb_standard_interface_descriptor{U8 bLength; /*Size of this descriptor in bytes*/U8 bDescrip