一、前言
使用CubeMX配置好STM32的USB设备后(我这里使用的是虚拟串口CDC),每下载一次程序就需要重新拔插一下USB线才能使用虚拟串口,我用的type c接口的数据线老是拔插和重新打开串口相当麻烦。于是研究了一下这个问题的原因,用简单的方法解决了。
二、问题产生原因分析
一般USB连接电脑后,电脑可以通过检查USB的D+引脚来判断USB是否有变化(STM32则对应PA12脚),由于STM32的USB D+ D- 数据线引脚一上电就默认初始化为USB FullSpeed设备,在电脑完成枚举之后如果对设备进行复位(包含重新下载程序),D+ D-电平一般不会发生跳变,因此电脑不会重新对此设备进行枚举。从而造成复位单片机后不重新拔插数据线USB虚拟串口无法使用的问题。
三、解决办法
基于以上原因,所以我们可以通过控制D+ D-引脚的电平变化来实现电脑对USB设备的重新枚举。
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
MX_USART3_UART_Init();
MX_USB_DEVICE_Init();
MX_TIM1_Init();
MX_TIM4_Init();
MX_SPI2_Init();
MX_IWDG_Init();
/* USER CODE BEGIN 2 */
由于项目是通过CubeMX生成的所以我们需要将代码放在USER CODE BEGIN和USER CODE END段内防止下次被CubeMX清空。
1. 寄存器代码实现
为了代码的简短我们操作GPIO使用寄存器来实现,实现方式如下。
GPIOA->CRH &= 0XFFF00FFF; // 清空PA11 PA12的配置
GPIOA->CRH |= 0X00033000; // 设置PA11 PA12为推挽输出
GPIOA->ODR |= 0<<11; // PA11 输出低电平
GPIOA->ODR |= 0<<12; // PA12 输出低电平
HAL_Delay(10); // 延迟10ms
必须将以上代码放在USB设备初始化之前才可以生效。这里我将代码放在了USB设备初始化函数内部的USER CODE段,也可以放在GPIO初始化函数中的末段。
void MX_USB_DEVICE_Init(void)
{
/* USER CODE BEGIN USB_DEVICE_Init_PreTreatment */
GPIOA->CRH &= 0XFFF00FFF; // 清空PA11 PA12的配置
GPIOA->CRH |= 0X00033000; // 设置PA11 PA12为推挽输出
GPIOA->ODR |= 0<<11; // PA11 输出低电平
GPIOA->ODR |= 0<<12; // PA12 输出低电平
HAL_Delay(10); // 延迟10ms
/* USER CODE END USB_DEVICE_Init_PreTreatment */
/* Init Device Library, add supported class and start the library. */
if (USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS) != USBD_OK)
其中控制低电平也可以使用库函数控制如下,模式配置使用库函数就不简短了,这里就不贴代码。
/* 库函数拉低PA11 PA12两个引脚 */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11|GPIO_PIN_12, GPIO_PIN_RESET);
添加好即可免去频繁拔插数据线的困扰了。