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 */