×
单片机 > 单片机程序设计 > 详情

STM32虚拟串口usb_printf函数及接收函数

发布时间:2020-06-09 发布时间:
|

环境: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库。


检测VCP连接状态

在中断函数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("连接成功rn");

        else if (hUsbDeviceFS.dev_state == USBD_STATE_SUSPENDED)

            printf("断开成功rn");

        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个字节;且每包数据以末尾追加  rn 表示一包数据接收完整。


 

当包长小于64个字节的时候

修改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("%srn", 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的倍数,最后末尾加上rn表示包的完整。


为什么会32位对齐,下面是STM32的源码:


typedef struct

{

  uint32_t data[CDC_DATA_HS_MAX_PACKET_SIZE/4];      /* Force 32bits alignment */

  ...

  ...

}

USBD_CDC_HandleTypeDef;


当包长大于64个字节的时候

发现一共发送了306个字节。但是MCU收到了318个字节。318怎么来的?


其实每64个字节加rn2个字节,最后一包数据不够4倍数再补充够4的倍数。


306 / 64 = 4......50


4 * 13 >= 50


也就是4 * (64 + rn) + 52个字节 + rn = 318。


故修改代码:以接收到rn为结束


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("%srn", 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("sudarootrn");

 

    while (1)

    {

        rxdata_printf();       

    }

}

附上测试代码:https://download.csdn.net/download/sudaroot/10975317


 


2019.07.24改进,源码没更新。

下面这个函数作用是避免我们每次下载复位后需要拨出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);

HA


『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
STM32单片机的复用端口初始化的步骤及方法