今天分享一下STM32F103如何实现虚拟串口,目标是让新手也能上手,如果谁没看懂或者照着做功能没有实现可以私信我,只要我看到一定回复。

新建STM32CubeMX工程

       本次使用的是STM32F103C8T6核心板,按以下步骤新建一个STM32CubeMX工程。

       步骤一 :先输入MCU型号搜索芯片,软件支持模糊搜索,然后在右侧选中正确的MCU后,点击“next”到下一步;

cubeMX配置USB虚拟 添加虚拟usb端口_核心板

       步骤二:输入工程名称,尽量不要含中文和特殊字符,其他保持默认设置就行,直接点“finish”按钮到下一步,此步骤很简单就不配图了;

       步骤三:进入工程配置界面我们首先点开System Core找到RCC选择时钟源,MCU外挂了一个8MHz晶振,我们选择外部时钟源;

cubeMX配置USB虚拟 添加虚拟usb端口_单片机_02

       步骤四:核心板上预留了一个SWD接口作为调试烧录口,我们在SYS中选择SWD作为调试口;

cubeMX配置USB虚拟 添加虚拟usb端口_单片机_03

       步骤五:接下来我们配置USB,展开Conectivity选择USB,把Device(FS)勾上,其他的保持默认设置就行了;

cubeMX配置USB虚拟 添加虚拟usb端口_cubeMX配置USB虚拟_04

       步骤六:再配置一下USB的第三方代码,展开Middleware选择USB_DEVICE,在Mode分区中选择虚拟串口(Virtual Port Com),其他保持默认设置就可以了;

cubeMX配置USB虚拟 添加虚拟usb端口_stm32_05

       步骤七:然后配置一下时钟,进入Clock Configuration界面,因为USB需要48MHz时钟,所以必须使用PLL,首先选择HSE作为时钟源,然后将倍频因子PLLMul改为X6,输出的USB时钟就是48MHz了,然后系统时钟选择PLLCLK即可,即48MHz;

cubeMX配置USB虚拟 添加虚拟usb端口_stm32_06

        步骤八:最后我们导出MDK工程和相关源码,就是我们常用的Keil工程。进入Project Manager界面,在Toolchain/IDE中选择MDK-ARM,其他保持默认就可以了,然后点击一下GENERATE CODE就可以生成工程和源码。

注:也可以生成EWARM、STM32CubeIDE等工程和相关源码,看个人开发习惯,驱动库的话一般默认是使用HAL库,如果FLASH比较紧张的话也可以选择LL库,在Advance Settings中可以选择,还可以选择是否生成外设初始化函数等等,功能很多,大家感兴趣可以自己去发掘。

cubeMX配置USB虚拟 添加虚拟usb端口_单片机_07

调试USB工程代码

       通过上述步骤我们就得到了一个USB的MDK工程,如下图所示。

cubeMX配置USB虚拟 添加虚拟usb端口_单片机_08

       调试步骤如下:

       步骤一、将核心板通过USB线连接到电脑,USB线同时可以给核心板供电,再将JLink仿真器连接到核心板的SWD接口;

cubeMX配置USB虚拟 添加虚拟usb端口_核心板_09

        步骤二、打开Keil工程,编译一下,报了一个错:

HTPA32X32\HTPA32X32.sct(7): error: L6236E: No section matches selector - no section to be FIRST/LAST.

       我们先来解决它,百度了一下,说是缺少启动文件,原因是生成Keil工程的路径带中文,我们将工程拷贝到一个不带中文的路径下,用STM32CubeMX重新打开.ioc文件,直接点击GENERATE CODE生成工程代码,结果好像还是没有生成启动文件,我们尝试把原先生成的所有文件删掉,只保留.ioc文件,重新生成工程代码后,打开工程就看到启动文件已经生成了,如下图,重新编译工程也通过了,问题解决!

cubeMX配置USB虚拟 添加虚拟usb端口_单片机_10

       步骤三:我们加一点代码,先在main.c中定义一个数组用来保存从USB接收到的数据,再定义一个变量用于数据计数:

uint8_t USB_RxBuf[100] = {0};
uint32_t USB_RxLen = 0;

       在main函数的while循环中增加以下代码,功能是只要收到USB数据就把数据原路发回去:

if(USB_RxLen > 0)
    {
        CDC_Transmit_FS(USB_RxBuf, USB_RxLen);
        USB_RxLen = 0;
    }

        因为main.c文件用到了CDC_Transmit_FS函数,我们需要将usbd_cdc_if.h文件包含进来才行,在main.c文件的开头新增以下代码:

#include "usbd_cdc_if.h"

        然后我们修改一下USB接收函数,先在菜单栏中点击View,选择Function Window,在Function窗口中找到usbd_cdc_if.c文件,找到里面的USB收接收函数CDC_ReceiveFS,添加如下所示(1)(2)(3)(4)代码:

extern uint8_t USB_RxBuf[100];            // (1)  
extern uint32_t USB_RxLen;                // (2)

static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 6 */

  memcpy(USB_RxBuf, Buf, *Len);  // (3)
  USB_RxLen += *Len;             // (4)
    
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
  USBD_CDC_ReceivePacket(&hUsbDeviceFS);
  return (USBD_OK);
  /* USER CODE END 6 */
}

       步骤四:编译OK后,点击DEBUG按钮开始调试,又报错了,原因是没有选对仿真器,还需要再配置一下,配置过程不再赘述,直接贴图自己看;

cubeMX配置USB虚拟 添加虚拟usb端口_cubeMX配置USB虚拟_11

cubeMX配置USB虚拟 添加虚拟usb端口_单片机_12

        步骤五:重新点击DEBUG按钮开始调试,打开串口助手后发现连接不上串口,改为直接下载程序,重新拔插一下USB线试一下,通上了,效果如下;

cubeMX配置USB虚拟 添加虚拟usb端口_cubeMX配置USB虚拟_13

       步骤六:虽然通上了,但是还有问题:为什么调试状态连不上串口呢?又去求助了一下度娘,度娘说“很简单,在GPIO初始化函数中将USB_DP引脚拉低再拉高就可以了”,按照度娘说的做果然没问题了,上代码给大家瞅瞅。

static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

    /*Configure GPIO pin : PA6 */
  GPIO_InitStruct.Pin = GPIO_PIN_12;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLDOWN;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET);
  HAL_Delay(50);
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_SET);
  HAL_Delay(50);
}