STM32F4驱动USB实现虚拟串口

实现目的

使用Dap-link和stlink的时候,就发现这些仿真器上并没有USB转TTL芯片,就可以实现USB转串口,实现虚拟串口,非常方便。这里实测得出,使用USB虚拟串口,可以轻松达到921600波特率,接近1M/s,因为这个虚拟串口实际就是USB通讯,使用USB通讯,模拟COM类通讯端口协议,实现串口通讯。

这个功能主要用于实现单片机通过USB线同上位机通讯,实现速率高(1M/s),稳定性强(USB线+差分信号),操作简单(串口通讯效果)的效果。

最终实现了单片机同上位机进行串口通讯,并编写了类似于HAL库串口通讯的USB串口通信操作函数,包括数据发送,printf发送,堵塞接收,中断接收等函数

注意,此方案单片机作为USB从机,同上位机(主机)通讯,不能够使用USB同使用了USB串口的其他设备通讯,因为他们也是从机。

cubemx初始化

STM32F407VET6+CubeMx+MDK5

系统时钟初始化

外部高速时钟初始化

修改debug方式

调试方式修改

GPIO配置

按键led配置

USB外设初始化

全速USB外设初始化

啥都不用改,把中断打开记得

中断配置

工程配置

修改时钟

配置工程

目录修改

keil5修改

勾选Micolib

初始化完成

实现HAL库uart通讯功能

简单使用系统函数 uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)进行通讯

while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */led_GPIO_Port->ODR^=led_Pin;CDC_Transmit_FS(Tx_Buffer,strlen((char*)Tx_Buffer));HAL_Delay(100);}

效果

设计USB_CDC_printf格式化输出函数

/*** @brief  USB虚拟串口格式化输出printf实现* @param  格式化输入* @retval 无*/
void usb_printf(const char *format, ...)
{va_list args;uint32_t length;va_start(args, format);length = vsnprintf((char *)UserTxBufferFS, APP_TX_DATA_SIZE, (char *)format, args);va_end(args);CDC_Transmit_FS(UserTxBufferFS, length);
}

USB虚拟串口主要使用CDC通讯协议,在usbd_cdc_if.c文件中,有相关函数描述,其中数据中断接收回调函数需要重点关注

static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{/* USER CODE BEGIN 6 *//* 定义外部变量 */extern uint16_t Rx_Date_Num,RX_goal_num;extern uint8_t UserRxBuffer[APP_RX_DATA_SIZE];extern uint8_t Rx_status;extern uint8_t* p;/* 保存接收到的数据 */Rx_date_save(Buf,UserRxBuffer,*Len);/* 如果接收到的数据量小于或等于缓冲区大小,增加接收数据的数量 */if(Rx_Date_Num<=APP_RX_DATA_SIZE)Rx_Date_Num+=*Len;/* 如果接收到的数据量大于缓冲区大小,将接收数据的数量设置为缓冲区大小 */elseRx_Date_Num=APP_RX_DATA_SIZE;/* 如果接收状态为0 */if(Rx_status==0){/* 如果接收到的数据量大于或等于目标数据量 */if(Rx_Date_Num>=RX_goal_num){/* 将用户接收缓冲区的数据复制到p指向的位置 */Rx_buffer_copy(p,UserRxBuffer,RX_goal_num);/* 减少接收数据的数量 */Rx_Date_Num-=RX_goal_num;/* 将接收状态设置为1 */Rx_status=1;}}/* 设置USB设备的接收缓冲区 */USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);/* 接收USB数据包 */USBD_CDC_ReceivePacket(&hUsbDeviceFS);/* 返回操作结果 */return (USBD_OK);/* USER CODE END 6 */
}

虚拟串口的接收方式是覆盖式的,相关缓存区大小由宏定义 APP_RX_DATA_SIZE 确定

比方说,本次接受了8个字节数据,分别是,“12345678”,然后发送了4个字节数据,分别是“abcd”,则缓存区数据变为“abcd5678”,原数据会被覆盖

这样是不利于我们接收数据的,如果我要接收128个字节的数据,为防止数据丢失,我会设置256个字节宽度的缓存区,原系统的接受到的数据会被及时转存到用户自定义的缓存区内,随取随用。

所以代码里我们定义了uint8_t UserRxBuffer[APP_RX_DATA_SIZE];用于存储用户想要接收的信息,放置被覆盖,并定义了相关函数,操作读取数据

堵塞型数据接收函数*

/*** @brief  这个函数用于接收USB虚拟串口的数据* @param  Rx_Buffer: 接收缓冲区* @param  num: 需要接收的数据数量* @param  overtime: 超时时间* @retval 如果接收成功,返回1,如果超时,返回0*/uint8_t usb_vbc_Receive(uint8_t* Rx_Buffer,uint16_t num,uint32_t overtime)
{uint32_t time=0;overtime=overtime/2;if(Rx_Date_Num>=num){Rx_buffer_copy(Rx_Buffer,UserRxBuffer,num);Rx_Date_Num-=num;return 1;}else{while(1){if(Rx_Date_Num>=num){Rx_buffer_copy(Rx_Buffer,UserRxBuffer,num);Rx_Date_Num-=num;return 1;}elsetime++;if(time>overtime)return 0;HAL_Delay(1);}}
}

中断型数据接收函数

内容较少,仅改变几个全局标志位的值,主要操作内容在中断回调函数里

/*** @brief  开启接收数据,不堵塞,完成接收任务后,全局变量Rx_status置一,否则为0* @param  Rx_Buffer: 接收缓冲区* @param  num: 需要接收的数据数量* @retval 无*/
void usb_vbc_Receive_It(uint8_t* Rx_Buffer,uint16_t num)
{p=Rx_Buffer;RX_goal_num=num;Rx_status=0;
}

中断回调函数内的操作

static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{/* USER CODE BEGIN 6 *//* 定义外部变量 */extern uint16_t Rx_Date_Num,RX_goal_num;extern uint8_t UserRxBuffer[APP_RX_DATA_SIZE];extern uint8_t Rx_status;extern uint8_t* p;/* 保存接收到的数据 */Rx_date_save(Buf,UserRxBuffer,*Len);/* 如果接收到的数据量小于或等于缓冲区大小,增加接收数据的数量 */if(Rx_Date_Num<=APP_RX_DATA_SIZE)Rx_Date_Num+=*Len;/* 如果接收到的数据量大于缓冲区大小,将接收数据的数量设置为缓冲区大小 */elseRx_Date_Num=APP_RX_DATA_SIZE;/* 如果接收状态为0 */if(Rx_status==0){/* 如果接收到的数据量大于或等于目标数据量 */if(Rx_Date_Num>=RX_goal_num){/* 将用户接收缓冲区的数据复制到p指向的位置 */Rx_buffer_copy(p,UserRxBuffer,RX_goal_num);/* 减少接收数据的数量 */Rx_Date_Num-=RX_goal_num;/* 将接收状态设置为1 */Rx_status=1;}}/* 设置USB设备的接收缓冲区 */USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);/* 接收USB数据包 */USBD_CDC_ReceivePacket(&hUsbDeviceFS);/* 返回操作结果 */return (USBD_OK);/* USER CODE END 6 */
}

其他相关数据操作函数

/*** @brief  这个函数用于复制接收缓冲区的内容,并将缓存区数据移位* @param  Buffer_get: 获取缓冲区* @param  Buffer_put: 放置缓冲区* @param  num: 要复制的元素数量* @retval 无*/void Rx_buffer_copy(uint8_t* Buffer_get,uint8_t* Buffer_put,uint16_t num)
{uint16_t i=0;for(i=0;i<num;i++)//复制数据{Buffer_get[i]=Buffer_put[i];}for(i=0;i<Rx_Date_Num-num;i++)//剩余数据移位{Buffer_put[i]=Buffer_put[i+num];}
}/*** @brief  这个函数用于将一个数组的内容复制到另一个数组中,而不会丢失接收数组中的原始数据* @param  src: 源数组* @param  dest: 目标数组* @param  n: 源数组中的元素数量* @retval 无*/
void Rx_date_save(uint8_t* src, uint8_t* dest, uint16_t n)
{uint16_t i=0,num=Rx_Date_Num;if(num+n>APP_RX_DATA_SIZE)return;//超出缓存区大小,这里直接停止。for(i=0;i<n;i++)dest[i+num]=src[i];
}/*** @brief  这个函数用于获取USB接收缓存区的数据数量* @param  无* @retval 返回接收的数据数量*/uint16_t usb_Rx_Get_Num(void)
{return Rx_Date_Num;
}

main函数

int main(void)
{/* USER CODE BEGIN 1 */uint8_t Rx_Buffer[32];uint8_t Tx_Buffer[32]="灵遨老六\n";/* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_USB_DEVICE_Init();/* USER CODE BEGIN 2 */usb_vbc_Receive_It(Rx_Buffer,16);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */led_GPIO_Port->ODR^=led_Pin;
//      time=HAL_GetTick();
//      CDC_Transmit_FS((uint8_t*)str, strlen(str));
//      if(usb_vbc_Receive(Rx_Buffer,16,500)==0)
//          usb_printf("超时:%d\n",HAL_GetTick()-time);
//      else
//          CDC_Transmit_FS((uint8_t*)Rx_Buffer, 16);if(Rx_status==1){CDC_Transmit_FS(Rx_Buffer, 16);usb_vbc_Receive_It(Rx_Buffer,16);}
//      CDC_Transmit_FS(Tx_Buffer,strlen((char*)Tx_Buffer));HAL_Delay(100);}/* USER CODE END 3 */
}

总结

使用USB虚拟串口,用起来很爽,波特率能跑很高,主要可以应用在同ROS主机通讯上;具体细致学习,可以参考开源Dap-link的代码。

dap-link

另外想使用DMA的话,F4的还没实现,H7的可以,速度应该可以跑很高。

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

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

相关文章

使用clion向STM32H7外置flash下载代码

简介 根据安福莱的STM32H7教程,H7单片机的QSPI外设是直接连到芯片内核上的,地址是0X90000000;那么就可以通过QSPI外设,将外置flash内存映射,并由此执行代码。相关操作在keil5上比较简单,配置点东西就行;可以参考安福莱教程。 这里要介绍的是在linux环境下没有keil5 IDE的…

Windows和Ubuntu网络文件共享

Windows11访问Ubuntu22.04共享文件夹 ubuntu配置右键选中需要共享的文件夹,选择属性->本地网络共享;选中共享此目录、允许其他人创建和删除这个文件夹里的文件、允许匿名登陆;并设置共享名配置好之后,点击修改共享。如果没有安装过samba服务,则会提升安装samba相关依赖…

Linux下使用clion+cubemx+openocd开发stm32

简述 后面需要在linux下学习驱动开发,然后不想玩虚拟机,就直接安装了双系统。用kde桌面玩的蛮开心的,就索性把win下的开发内容都搬过来吧 在Linux下开发STM32,使用Clion和Cubemx开发,openocd调试,关于芯片下载,编译器选项,代码起始内容都需要自行通过工程的配置文件修改…

银行视频监控智能分析

视频监控智能分析银行系统通过安装在银行的营业厅、取款机处或者银行柜台以及银行门口等区域的各大品牌的终端监控摄像头,视频监控智能分析在系统后台软件上的视频画面内设置智能分析区域,通过上面的操作实现对银行的7*24小时的智能视频监控分析报警,对进出人员行为进行智能…

智慧港口视频智能分析系统解决方案

港口视频监控智能分析系统是对港口各处的监控回传的视频流利用视频监控智能分析技术进行智能分析处理,视频监控智能分析系统将处理结果然后传送到中心管理服务器或者流媒体服务器进行有效管理。监控中心设立在港口控制中心,主要用于相关工作人员进行远程监控和管理。港口监控…

电力视频监控系统

电力视频监控系统通过对电力工程建设领域利用电力视频监控系统进行违规违章操作检测及其他安全区域监测,电力视频监控系统可以降低或减少安全事故造成的人员伤害和设备损害,提升公司社会形象,杜绝违规行为的发生。视频智能分析系统可以进一步强化安全管理,能够降低电力安全…

2024 最新上海市提取公积金缴纳房租 All In One

2024 最新上海市提取公积金缴纳房租 All In One 提取公积金 图解教程2024 最新上海市提取公积金缴纳房租 All In One最新版(亲测可用 ✅)随申办市民云 Apphttps://apps.apple.com/cn/app/随申办市民云/id732618720 提取公积金 图解教程步骤 图解 备注1. 打开随申办 App / 小程序…

Windows上使用VTune分析PyTorchExtension调用的Cpp程序

概述 最近在实现一个通过PyTorch Extension扩展PyTorch算子的C++算法,需要分析代码的运行瓶颈进行针对性优化。Intel VTune就是一个能从汇编级和源码级分析CPU运行瓶颈的工具。由于不明原因我没在服务器上跑通命令行版的VTune,所以把程序搬到Windows下分析了,因此记录一下Wi…

[LeetCode] 80. Remove Duplicates from Sorted Array II

原来leetcode使用Count也不需要import collections class Solution:def removeDuplicates(self, nums: List[int]) -> int:# len =0if len(nums) == 0:return 0# elsecountList = Counter(nums)countModify = {key:min(value,2) for key,value in countList.items()}ret = […

个人标语

从来不觉得自己比任何人差,讨厌懒惰,骄傲自满与妄自菲薄。

IDER如何生成数据库的ER图?

1.打开 IDEA,并连接到 MySQL数据库这里连接到你的数据库 2.在菜单栏中选择"View"->"Tool windows"->"Database" 3.在"Database" 工具窗口中,展开你想要査看的数据库,并选择"ER Diagrams"。 4.右键单击"ER Dia…

服务器系统瘫痪系统损坏数据恢复

故障服务器数据恢复环境: 一台故障服务器;共有4块146G SAS硬盘组成的RAID5;故障服务器分析检测: 服务器在运行过程中系统瘫痪,重装系统后系统损坏。系统中有数据库、网站程序与网页。 服务器数据恢复过程: 1、对全盘reiserfs树节点之间的关联确定原来的reiserfs分区位置镜…

服务器瘫痪,里面存有mysql数据库表结构

最上层,大多数基于网络服务器的工具或服务都有类似架构。 第二层,大多数MySQL的核心服务,包括查询解析、分析、优化、缓存以及所有的内置函数,所有跨存储引擎的功能都在这一层实现:存储过程、触发器、视图等。 第三层,存储引擎负责MySQL中数据的存储和提取。服务器通过AP…

误操作还原VMware虚拟机数据恢复

故障服务器数据恢复环境: 一台故障物理服务器上迁移到ESXI上的虚拟机。故障服务器检测与分析: 虚拟机的数据恢复迁移的状态,数据全部丢失。还原快照操作与删除数据,将该设备上所有虚拟机关机或迁移到其他ESXI上。 恢复数据之前需要先了解vmfs文件系统的底层结构。vmfs文件系…

存储中NAS卷数据丢失数据恢复

故障服务器数据恢复环境: 一台故障存储,支持SAN和NAS存储协议。服务器故障检测与分析: 存储一个NAS卷中的数据丢失,卷大小为2T,经过检查发现丢失数据的类型为office文档、PDF文件、图片文件(JPG、JPEG、PNG等)。 服务器数据恢复过程: 1、准备好备份服务器,将备份服务器…

系统误操作删除数据恢复

故障服务器环境故障: 一台有文件系统的故障服务器,误操作删了服务器上的数据。服务器数据恢复过程: 1、对故障服务器所有硬盘进行扇区级镜像备份。 2、根据文件系统结构和元信息分析文件系统盘序、使用盘数和校验盘个数等信息。 3、全盘扫描节点入口。 4、扫描所有硬盘的底层…

服务器意外断电导致linux数据目录项数据丢失数据恢复

故障服务器数据恢复环境:​ 一台故障列服务器;做的是linux操作系统。服务器故障检测与分析:​ 因机房在运行中,意外断电导致服务器文件丢失。 服务器数据恢复过程:​ 1、将linux服务器连接到数据恢复服务器,以只读模式进行服务器数据备份,所有的数据恢复操作都在备份上进…

IBM服务器硬盘离线数据恢复

服务器数据恢复故障描述: IBM X3850服务器,服务器挂载了5块73G SAS硬盘组成raid5磁盘阵列。2块硬盘离线,RAID崩溃。 服务器存储有oracle数据库,数据恢复+操作系统复原。服务器磁盘阵列数据恢复流程: 1、关闭服务器做数据恢复,服务器关闭状态保护故障服务器原始状态。 2、…

关于家居强电电路模拟程序3和4总结

前言:对于家居强电电路的3和4总结来说,考察了正则表达式,类的运用,程序与设计的基础使用,ArristList 类的使用等等知识点。相对于家居强电电路3来说,4的难度大得多。在3的基础上,加上了引脚的输出电压,对于输出通过引脚的对应电压。那么要判断更多的东西(比如说引脚是…

我的第三次oop

阅读目录 前言 第四次PTA 第五次PTA 第六次PTA 总结 前言 一:对于java的进一步认识 Java是一种强大、跨平台的编程语言,它以面向对象为核心,拥有自动内存管理、丰富的API和广泛的社区支持。Java的广泛应用领域包括Web开发、移动应用开发、企业级应用以及大数据处理等,其易用…