STM32F4的USB接口学习笔记-虚拟串口

实现通过USB虚拟串口与PC正常通讯的功能

1 STM32CubeMX配置

1.1 USB接口配置

单片机做从机,所以Mode选择Device,其它设置保持默认即可
在这里插入图片描述
使能USB接口的中断:
在这里插入图片描述
设置IP,选择Virtual Port Com
在这里插入图片描述
其它设置项可保持不变,但要注意最下面两个设置项:Buffer Size,usbd_cdc_if.c中会根据这个值定义两个数组,请根据需要自行设置大小,也可以不使用库提供的buffer。
在这里插入图片描述
这两个数组为全局变量,尽量根据实际需要调整大小。
在这里插入图片描述

1.2 时钟配置

USB接口使用的时钟频率为48MHz
在这里插入图片描述
至此,配置完成,生成代码

2 编写测试程序

2.1 编写打印信息到USB虚拟串口的函数

方法一,编写打印函数:

#include <stdarg.h>
void usb_printf(const char *format, ...)
{
    va_list args;
    uint32_t length;
 
    va_start(args, format);
    length = vsnprintf((char *)UserTxBufferFS, 2048, (char *)format, args);
    va_end(args);
    CDC_Transmit_FS(UserTxBufferFS, length);
}

这里面的核心是CDC_Transmit_FS()函数,先用vsnprintf函数把要打印的数据整合到UserTxBufferFS数组(前面有定义大小)中,再调用CDC_Transmit_FS函数将数组中的数据发送出去。
方法二,在putc函数中实现发送功能:

int fputc(int ch, FILE *f)
{
	uint8_t buf = (uint8_t)ch;
	CDC_Transmit_FS(&buf, 1);
}

该方法不使用UserTxBufferFS数组,节省内存,并且程序中的打印函数可以继续使用printf,而不需要更改。
但用该方法测试时不成功,每次只会发送一个字符,后来找到原因,是fputc调用太快造成USB数据包堵塞,因为USB发送是分包进行的,包大小最小64字节,添加一个小延时即可解决:

int fputc(int ch, FILE *f)
{
	uint8_t buf = (uint8_t)ch;
	CDC_Transmit_FS(&buf, 1);
	HAL_Delay(1);
}

但这样会导致程序执行效率变慢,最好还是采用方法一。

2.2 接收数据,以字符型数据为例

USB收到数据后,会调用usbd_cdc_if.c文件中的CDC_Receive_FS函数,因此我们需要修改该函数,处理接收到的数据,本人采用的FIFO方式保存数据

static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
	/* USER CODE BEGIN 6 */
	Buf[*Len] = 0;
	PC_RX_Fifo->PushString((char *)Buf);
	
	USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS);
	USBD_CDC_ReceivePacket(&hUsbDeviceFS);
	return (USBD_OK);
	/* USER CODE END 6 */
}

添加的代码只有前面两行,Buf[*Len]用于添加一个字符结束符,防止PushString保存出错。
PushString函数用于将Buf中的内容全部存入FIFO中,遇到0则停止,因此有效数据中不能有0。
至此,接收数据的功能也完成了。

2.3 处理接收到的数据

编写数据处理函数:
PopLine函数用于从FIFO中取出一行数据,需设置最多取出的字符数量,防止出错

char buffer[100];						//存放从FIFO中取出的数据
while (PC_RX_Fifo->Lines > 0)			//串口接收缓冲区中有整行的数据
{
	uint16_t len = PC_RX_Fifo->PopLine(buffer, sizeof(buffer));
	if (len == 0)						//数据长度为0,表示用户按下了回车键,发送笑脸符号
	{
		printf("@_@");
	}
	else if (MyMSH_ExecCmd(buffer) == 0)		//未识别的命令
	{
		printf("NG,%s,Undefined command!\r\n@_@", buffer);
	}
}

2.4 测试

先编写两个命令函数:

//命令,获取治具名称
void Fixture(char * Payload)
{
	printf("OK,%s,%s\r\n@_@", __func__, Fixture_Name);
}
MSH_CMD_EXPORT(Fixture, Get fixture name);

//命令,读取固件版本
void FW_Ver(char * Payload)	
{
	printf("OK,%s,%s %s %s\r\n@_@", __func__, FW_Version, __DATE__, __TIME__);
}
MSH_CMD_EXPORT(FW_Ver, Get firmware version);

用PC串口助手发送命令,波特率可以随意设置:
在这里插入图片描述
测试几条命令,发现单片机都有正常回应:
在这里插入图片描述

3 解决每次下载完程序串口都丢失的问题

每次下载完程序串口都会丢失,单片机复位一次就好了,采用博主sudaroot的方法解决了该问题,原文链接: https://blog.youkuaiyun.com/sudaroot/article/details/86627853
原理:PC的usb内部两根数据线都接着下拉电阻,当检测任一个任一根数据线有高电平代表有设备接入初始化。
下面这个函数作用是上电时把两个USB IO拉低,相当于手动断开USB线,然后进行MX_USB_DEVICE_Init()初始化的时候会正确初始化这两个IO,避免我们每次下载复位后需要拨出USB再插上才能用。

static void USB_Status_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
 
    /* GPIO Ports Clock Enable */
    __HAL_RCC_GPIOA_CLK_ENABLE();
 
    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11 | GPIO_PIN_12, GPIO_PIN_RESET);
 
    GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLDOWN;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
    //假如不行的话,下面的延时加长即可。
    HAL_Delay(20);
}

使用方法:在main()函数中的 SystemClock_Config()函数之后调用USB_Status_Init();
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值