Skip to content

USB 实现串口设备

1. 硬件连接

软件版本: stm32cubmx:6.2 keil 5

硬件:

  1. stm32F103C8T6最小系统板
  2. 下载器
  3. USB线 alt text

从bilibili 工房子购买开发版 https://gf.bilibili.com/item/detail/1106139112alt text

2. CubeMX生成代码

选择芯片型号,我这边是C8t6 alt text

debug选择 SW模式 alt text

配置外部时钟输入 alt text 配置USB device的功能 alt text

将USB的模式配置成COM口的功能 alt text

时钟树配置,如果前面没有配置外部晶振输入,这边无法配置成功。正常的话自动会配置成功 alt text

工程配置,配置成对应keil的版本 alt text

框出来的位置有时候因为尺寸太小会出错,有人说默认的也可以,反正我改了,如下图 alt text

3. 编写代码

实现串口输出ABCD,需要更改的部分

C
//1.添加头文件
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usb_device.h"
#include "gpio.h"
#include "usbd_cdc_if.h"

更改main函数

C
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void USB_Disconnected(void) {
    __HAL_RCC_USB_FORCE_RESET();
    HAL_Delay(200);
    __HAL_RCC_USB_RELEASE_RESET();
 
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_GPIOA_CLK_ENABLE();
 
    GPIO_Initure.Pin = GPIO_PIN_11 | GPIO_PIN_12;
    GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Initure.Pull = GPIO_PULLDOWN;
    GPIO_Initure.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_Initure);
 
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET);
    HAL_Delay(300);
}

/* USER CODE END 0 */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* 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 */
  //USB 卸载操作,取消按复位键后无法识别的问题
  USB_Disconnected();

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USB_DEVICE_Init();
  /* USER CODE BEGIN 2 */
	//2.添加数组
	unsigned char buff[10] = {"abcd\n\r"};
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		//3. 在while循环中添加如下代码
		HAL_Delay(1000);
		CDC_Transmit_FS(buff,sizeof(buff)); //USB 串口发送数据
  }
  /* USER CODE END 3 */
}

串口助手 alt text

读取输入的值和上一次是否相同,从而输出不同的值 首先需要将main函数恢复成初始的状态 更改下面的函数 CDC_Receive_FS();这个函数在usbd_cdc_if.c的文件下面的位置 初始代码

C
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 6 */
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
  USBD_CDC_ReceivePacket(&hUsbDeviceFS);
  return (USBD_OK);
  /* USER CODE END 6 */
}

将代码改成如下的样子

C
uint8_t a=0,a1=1;
unsigned char buff1[4] = {"abcd"};
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{	
	
	a = *Buf;		

	if(a == a1)
				CDC_Transmit_FS(Buf,*Len);//自收自发
	else 
	{
			CDC_Transmit_FS(buff1,sizeof(buff1));
			a1 = a;
	}
  /* USER CODE BEGIN 6 */
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);

  USBD_CDC_ReceivePacket(&hUsbDeviceFS);
	//CDC_Transmit_FS(Buf,*Len);//自收自发


  return (USBD_OK);
  /* USER CODE END 6 */	

}

会对比此次的和上一次的差异,如果一样则输出 接收到的值,如果不一样则输出abcd

PS:有一个BUG,数字大了后识别会出错,只能识别发送的第一个数据的差异 alt text

3.4 串口定向printf

参考文档: 文件的配置教程 https://www.jianshu.com/p/579783d28044

1.驱动下载链接 https://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-utilities/stsw-stm32102.html 2. 代码操作 https://blog.csdn.net/u010779035/article/details/104369515

假装的Print—使用sprintf 这个函数将数据处理到串口发送的数组中,串口通过设置中断或者定时发送,进行数据的发送,但需要占用比较大的数据资源。实时性不确定。

3.5 串口接收的数据存到数组中

如下代码需要在main函数中进行更改

定义初始化函数

C
/* USER CODE BEGIN PTD */
uint8_t uart_number[64]="nihao\n\r";
/* USER CODE END PTD */

如下使用串口发送函数,将数组 uart_number 发送出去

C
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		CDC_Transmit_FS(uart_number,sizeof(uart_number)); //USB 串口发送数据
		HAL_Delay(1000);	
		
  }
  /* USER CODE END 3 */

CDC_Receive_FS();这个函数在usbd_cdc_if.c的文件下面的位置

C

extern uint8_t uart_number[64];
uint8_t* aa;
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
	uint32_t i;
	
  /* USER CODE BEGIN 6 */
	
	
	//会对比此次的和上一次的差异,如果一样则输出 接收到的值,如果不一样则输出abcd
	//最多11个汉字 64个16进制数 

	memset(uart_number, 0, sizeof uart_number); //清除数组uart_number
	
	
	//循环函数,将接收到的数字,存到数组中
	aa = Buf;	
		for(i = 0;i < *Len;i++)
	{
		uart_number[i] = *aa;
		aa++;
	}
			

	
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
  USBD_CDC_ReceivePacket(&hUsbDeviceFS);
  return (USBD_OK);
  /* USER CODE END 6 */
}

4. 后记

另外PS一个bug: 可能由于数组的问题,在接收数据太大后会出现溢出?//最多11个汉字 64个16进制数 能够满足基本的使用,但是不推荐太长的数据,或者控制单次发送的数据长度,分多次发送。

回答:只能接收64个字节是因为CDC_Receive_FS里面接收一包数据最长只有64字节,超过后要重新设置USBD_CDC_SetRxBuffer的接收pbuff地址

#bug讨论#"有一个BUG,数字大了后识别会出错,只能识别发送的第一个数据的差异" *Buf就是usb接收缓存字符串,字符串不能直接等号比较,需要使用字符串比较函数。

解决BUG:关于在按下reset之后,串口可以识别,但是无法打开的问题 在USB初始化前强制USB时钟复位

C
void USB_Disconnected(void) {
    __HAL_RCC_USB_FORCE_RESET();
    HAL_Delay(200);
    __HAL_RCC_USB_RELEASE_RESET();
 
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_GPIOA_CLK_ENABLE();
 
    GPIO_Initure.Pin = GPIO_PIN_11 | GPIO_PIN_12;
    GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Initure.Pull = GPIO_PULLDOWN;
    GPIO_Initure.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_Initure);
 
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET);
    HAL_Delay(300);
}

再USB初始化之前,执行一下这些就行了,相当于告诉电脑断开链。这样按下复位键后会自动断开连接,软件上重新选择com口和重新打开旧可以正常识别。

PS:这样的方式就使得USB虚拟的串口,会有一段空白的阶段,使得其无法输出初始化状态下的串口信息。有着一丝的鸡肋?

上面的程序放在如下的main.c 文件中

C
  /* USER CODE BEGIN SysInit */
	//注销USB
   USB_Disconnected();

  /* USER CODE END SysInit */