..\STM32Cube\Repository\STM32Cube_FW_F4_V1.23.0\Projects\STM324xG_EVAL\Applications\USB_Device\CDC_Standalone\MDK-ARM
STM32CubeMX STM32F429IGT6 STlink
首先要确保硬件电路USB部分没问题;USB相关的概念知识大概需要了解一下,网上挺多这类文章的,自行百度。
点击USB_OTG_FS,模式选择Device_Only,其他保持默认。
点击USB_DEVICE,选择IP 为VPC(虚拟串口),其他保持默认。
我使用的芯片是F429IGT6,最大时钟180MHz,但是USB时钟必须为48MHz(详情看STM32中文参考手册930页),180MHz是分频不出来48MHz的USB时钟,所以把系统配置成168MHz就能分频出48MHz的USB时钟。
堆空间需要改大一点,不然在USB插入电脑的时候,设备管理器会显示虚拟串口设备黄色感叹号。因为在USB插入电脑,STM32会创建一个实例,malloc申请内存,但内存不足的时候就失败了,驱动工作不正常了。
源码在usbd_cdc.c中:
pdev->pClassData = USBD_malloc(sizeof (USBD_CDC_HandleTypeDef));
最后生成代码即可,打开工程。
图中Application/User文件夹中多了几个文件:
usb_device.czhi只有一个USB设备函数初始化函数 MX_USB_DEVICE_Init()。
usb_conf.c是USB协议参数、IO初始化、中断回调函数、端点打开关闭停止操作等等函数。
usbd_cdc_if.c有虚拟串口的接收和发送等函数。
usb_desc.c有USB的描述符和USB枚举处理等。
文件夹Middlewares/USB_Device_Library是STM32Cube库。
在中断函数OTG_FS_IRQHandler
HAL_PCD_IRQHandler(&hpcd_USB_OTG_FS);
HAL_PCD_ConnectCallback(hpcd); //连接事件回调
HAL_PCD_DisconnectCallback(hpcd); //断开事件回调
发现两个连接状态事件回调函数,但是经过测试发现这两个回调函数根本不会发生。具体往下就没去深究了。
USBD_StatusTypeDef USBD_LL_DevDisconnected(USBD_HandleTypeDef *pdev)
{
/* Free Class Resources */
pdev->dev_state = USBD_STATE_DEFAULT;
pdev->pClass->DeInit(pdev, pdev->dev_config);
return USBD_OK;
}
void HAL_PCD_DisconnectCallback(PCD_HandleTypeDef *hpcd)
{
USBD_LL_DevDisconnected((USBD_HandleTypeDef*)hpcd->pData);
}
但是偶然发现断开事件有改变一个变量pdev->dev_state = USBD_STATE_DEFAULT;
所以发现hUsbDeviceFS.dev_state的状态才是真正的连接状态标志位。
/* Device Status */
#define USBD_STATE_DEFAULT 1 //初始化状态
#define USBD_STATE_ADDRESSED 2 //建立地址
#define USBD_STATE_CONFIGURED 3 //配置完成,连接成功
#define USBD_STATE_SUSPENDED 4 //usb挂起,断开成功
检测USB状态的函数
void VCP_Status(void)
{
static uint8_t old_status = 0;
if(hUsbDeviceFS.dev_state != old_status)
{
if(hUsbDeviceFS.dev_state == USBD_STATE_CONFIGURED)
printf("连接成功\r\n");
else if (hUsbDeviceFS.dev_state == USBD_STATE_SUSPENDED)
printf("断开成功\r\n");
old_status = hUsbDeviceFS.dev_state;
}
}
写一个usb_printf打印函数,在usbd_cdc_if.c里面末尾USER CODE BEGIN及USER CODE END之间添加
/* USER CODE BEGIN PRIVATE_FUNCTIONS_IMPLEMENTATION */
#include
void usb_printf(const char *format, ...)
{
va_list args;
uint32_t length;
va_start(args, format);
length = vsnprintf((char *)UserTxBufferFS, APP_TX_DATA_SIZE, (char *)format, args);
va_end(args);
CDC_Transmit_FS(UserTxBufferFS, length);
}
/* USER CODE END PRIVATE_FUNCTIONS_IMPLEMENTATION */
1、usb虚拟串口每次接收最大的数据包ReceivePacket是64个字节;且每包数据以末尾追加 \r\n 表示一包数据接收完整。
修改USB接收打印函数,在usbd_cdc_if.c里先找到static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)这个函数,
把它改成下图类似:
PC的usb虚拟串口收到MCU的数据通过UARTx发送回PC。演示一下发现什么问题:
//Len是每包数据的有效数据长度。值不会超过64
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
printf("%s\r\n", Buf);
memset(Buf, 0, APP_RX_DATA_SIZE);
return (USBD_OK);
}
可以看到第一次发送内容 01 >>> MUC收到的数据是:01 81 7F 04 0D 0A 。数据异常,多了中间的 81 7F 04。
可以看到第二次发送内容 01 02 03 04 >>> MUC收到的数据是:01 02 03 04 0D 0A 。数据正常。
注意static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)的 *Len 是每包数据真实有效长度;
如
第一次发送的内容,*Len是等于1的;
第二次发送的内容,*Len是等于4的;
*Len最大不会超过64。
出现第一种情况的原因是:当包长小于64个字节的时候,数据会强制32bit对齐。也就是发的字节必须要是4的倍数,不够补够4的倍数,最后末尾加上\r\n表示包的完整。
为什么会32位对齐,下面是STM32的源码:
typedef struct
{
uint32_t data[CDC_DATA_HS_MAX_PACKET_SIZE/4]; /* Force 32bits alignment */
...
...
}
USBD_CDC_HandleTypeDef;
发现一共发送了306个字节。但是MCU收到了318个字节。318怎么来的?
其实每64个字节加\r\n2个字节,最后一包数据不够4倍数再补充够4的倍数。
306 / 64 = 4......50
4 * 13 >= 50
也就是4 * (64 + \r\n) + 52个字节 + \r\n = 318。
故修改代码:以接收到\r\n为结束
typedef struct{
uint32_t rxlen;
uint32_t flag;
}VcpRx_t;
VcpRx_t temp = {
.rxlen =0,
.flag = 0
};
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
/* USER CODE BEGIN 6 */
temp.rxlen = temp.rxlen + (*Len);
if(temp.rxlen < APP_RX_DATA_SIZE && UserRxBufferFS[temp.rxlen - 2] != 0x0d \
&& UserRxBufferFS[temp.rxlen - 1] != 0x0a)
{
//---继续接收---------------
USBD_CDC_SetRxBuffer(&hUsbDeviceFS,UserRxBufferFS + temp.rxlen);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
}
else temp.flag = 1; //接收完成
return (USBD_OK);
/* USER CODE END 6 */
}
//接收完成后通过UART打印出来
void rxdata_printf(void)
{
if(temp.flag)
{
printf("%s\r\n", UserRxBufferFS);
temp.flag = 0;
temp.rxlen = 0;
memset(UserRxBufferFS, 0, APP_RX_DATA_SIZE);
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
}
}
main函数
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USB_DEVICE_Init();
MX_USART2_UART_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("sudaroot\r\n");
while (1)
{
rxdata_printf();
}
}
附上测试代码:https://download.csdn.net/download/sudaroot/10975317
下面这个函数作用是避免我们每次下载复位后需要拨出USB再插上才能用。如果不行的话可以把HAL_Delay()延时加大一些。
原理:和usb硬件相关。PC的usb内部两根数据线都接着下拉电阻,当检测任一个任一根数据线有高电平代表有设备接入初始化。下面代码就是模拟,上电把两个STM32的USB IO拉低,相当于手动断开USB线,然后进行MX_USB_DEVICE_Init()初始化的时候会正确初始化这两个IO。
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);
/*Configure GPIO pin : W25Q256_CS_Pin */
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(2);
}
使用方法:
在main()函数中的 SystemClock_Config()函数调用后使用USB_Status_Init();
VCP通讯走的是pack,故和串口调试助手的波特率无关。无论波特率多少,对数据无影响。
全篇完。
本人博客仅仅代表我个人见解方便记录成长笔记。
若有与 看官老爷见解有冲突,我坚信看官老爷见解是对的,我的是错的。
感谢~!