stm32 usb的学习与使用笔记

参考教程

  1. 【STM32 + CubeMX】 USB 虚拟串口通信
    主要参考这个,写的非常的详细,可以直接看这边文章,本文为指路
  2. stm32-USB使用记录(一)
  3. 嵌入式STM32 USB通信

步骤


编译无错

1、包含 USB接口 的头文件

打开 main.c文件,大约第26行,配对的 /* USER CODE ... Includes */ 注释之间,

添加:#include "usbd_cdc_if.h"

2、增加 USB模拟插拔

为了避免调试期间频繁地手动操作,我们可以在程序开跑后、USB初始化前,用代码把PA12置低,使D+线为低电平,持续一段时间,模拟USB拔出动作,令主机认为设备已断开连接,释放端口;

然后,当程序运行到后面的USB初始化函数时,PA12会被正常配置(DP线电平被置高),USB主机就会"发现"有设备插入,开始尝试枚举、配置;

具体操作:

打开 usbd_conf.c 文件,大约第70行附近,找到HAL_PCD_MspInit ( )函数;

在两对 /* USER ... 0 */ 注释之间,约75行,添加PA12引脚置低电平操作, 如下(可复制):

c 复制代码
    __HAL_RCC_GPIOA_CLK_ENABLE();                   // 使能GPIOA端口
    GPIO_InitTypeDef GPIO_InitStruct = {0};         // 声明结构体; 如果与文中位置相同,这行可不写
    GPIO_InitStruct.Pin = GPIO_PIN_12;              // 引脚PA12, 即D+
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;     // 引脚工作模式
    GPIO_InitStruct.Pull = GPIO_PULLDOWN;           // 下拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;    // 引脚反转速度
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);         // 初始化
    HAL_Delay(5);                                   // 持续片刻

3. 发送数据的函数

c 复制代码
uint8_t  CDC_Transmit_FS ( uint8_t* Buf,  uint16_t Len );

函数接受两个参数:数据缓冲区的地址、字节数。

如果USB设备正忙,它会返回USBD_BUSY状态。

这个函数的作用是设置传输数据的缓冲区,并标记数据包为待发送。数据并非立刻发出,而是被存储在USB外设的缓冲区中,等待主机轮询请求传输。

1、接收方式的简述

当USB CDC接收到来自USB主机的数据时,触发中断进入中断函数,继而自动调用接收回调函数:

复制代码
int8_t  CDC_Receive_FS (uint8_t* Buf, uint32_t *Len);  

uint8_t* Buf: 指向接收缓冲区的指针,即数据缓存的地址。

uint16_t* Len: 当前数据包的字节数。

我们就在这个回调函数里,处理接收到的数据!

它在 usbd_cdc_if.c 文件,位于发送函数的正上方;

2、接收示范

通过串口助手,发送字符串

STM32(设备端)收到数据后,把收到的字节数、字符串,发回串口助手显示(主机端)

在函数内的注释行 /* USER CODE BEGIN */ 下方,添加4行自定义代码(可复制)。

c 复制代码
    char myStr[64] = {0};                                                     // 定义一个数组,用于存放要输出的字符串
    sprintf(myStr, "\r\r收到 %d 个字节;\r内容是:%s\r\r", *Len, (char *)Buf); // 格式化字符串
    CDC_Transmit_FS((uint8_t *)myStr, strlen(myStr));                         // 发送
    memset(Buf, 0, 64);                                                       // 处理完数据,清0接收缓存;

问题解决

1. 优化发送,能连续发送

先打开 CDC_Transmit_FS ( ) 函数,看看函数原型。

修改:

注释掉刚才 if 体的3行代码;

增加等待发送空闲、判断超时,如下6行;

整个CDC_Transmit_FS ( ) 函数,如下:

c 复制代码
uint8_t CDC_Transmit_FS(uint8_t *Buf, uint16_t Len)
{
    uint8_t result = USBD_OK;
    /* USER CODE BEGIN 7 */
    
    USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef *)hUsbDeviceFS.pClassData; // 获得设备的状态信息结构体
    // if (hcdc->TxState != 0){
    // return USBD_BUSY;
    // }
    uint32_t timeStart = HAL_GetTick();
    while (hcdc->TxState)
    {
        if (HAL_GetTick() - timeStart > 20)
            return USBD_BUSY;
    }
 
    USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
    result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);
 
    /* USER CODE END 7 */
    return result;
}

2.接收优化(在外部处理数据)

操作共4个步骤,具体如下:

1、增加全局变量

在usbd_cdc_if.c文件大约97行,配对的注释内,定义两个变量:

c 复制代码
/* USER CODE BEGIN PRIVATE_VARIABLES */
uint8_t  myUsbRxData[64] = { 0 };   // 接收到的数据
uint16_t myUsbRxNum = 0;            // 接收到的字节数
 
/* USER CODE END PRIVATE_VARIABLES */

现在,它俩只是本地变量,等会要在main中用extern再声明一次,才能被外部调用。

2、修改接收回调函数

在CDC_Receive_FS() 里,删除我们上节增加的测试代码;

把Buf和*Len的数据,复制到我们刚才的两个变量里。函数修改成:

c 复制代码
static int8_t CDC_Receive_FS(uint8_t *Buf, uint32_t *Len)
{
    /* USER CODE BEGIN 6 */
    // 把Buf里面的数据,复制到外部缓存
    memset(myUsbRxData, 0, 64);                     // 清0缓存区 
    memcpy(myUsbRxData, Buf, *Len);                 // 把接收到的数据,复制到自己的缓存区中
    myUsbRxNum = *Len;                              // 复制字节数    
    memset(Buf, 0, 64);                             // 处理完数据,清0接收缓存;                       
    
    // CubeMX生成的代码,保留    
    USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);   // 设置下-个接收缓冲区
    USBD_CDC_ReceivePacket(&hUsbDeviceFS);          // 启动下一个数据包的接收
    return (USBD_OK);                               
    /* USER CODE END 6 */
}

现在,数据接收部分,已处理好了。

以后回调函数运行时,只复制数据至外部缓存(备用),中断时间占用极短,不会影响下包接收。

3、 在外部用extern声明变量,令外部可调用数据

外部,哪个文件里要使用CDC接收的数据,就在这个文件里,用extern声明那俩变量。

如,可以在LCD文件,也可以在SD卡的文件中,都行。

建议在main.h文件中声明,其它文件再#include "main.h",这样,可以令变量全局可用。

打开 main.c,右击空白,点击"Toggle Header/Code File",可以跳转到头文件:main.h

在main.h中,大约38行,找到 配对的注释行 /* USER CODE BEGIN ET */

用 extern 再次声明刚才两个变量。如下(可复制):

注意,是只声明,不要赋值,否则编译错误。

c 复制代码
/* USER CODE BEGIN ET */
extern uint8_t myUsbRxData[ ] ;
extern uint16_t myUsbRxNum ;
 
/* USER CODE END ET */

4、使用接收到数据

在main.c的while循环中,通过判断myUsbRxNum的值,只要大于0,就表示收到数据了

记得每次处理完数据,把myUsbRxNum置0,以便于下一轮的判断。

再次烧录,烧录程序。

打开串口助手,发送测试文本,可以发现,能成功收到STM32发过来的回传数据。

小结

本文主要记录本人使用usb的过程,具体直接看原博主的文章,更详细。

原文:【STM32 + CubeMX】 USB 虚拟串口通信

相关推荐
陈桴浮海2 小时前
【Linux&Ansible】学习笔记合集七
linux·学习·ansible
H Corey2 小时前
Java字符串操作全解析
java·开发语言·学习·intellij-idea
暴躁小师兄数据学院2 小时前
【WEB3.0零基础转行笔记】Go编程篇-第6讲:函数与包
笔记·golang·web3·区块链·智能合约
WZ188104638692 小时前
软件测试人员怎样学习AI
人工智能·学习
EverydayJoy^v^2 小时前
RH134学习进程——十二.运行容器(4)
学习
蒸蒸yyyyzwd2 小时前
分布式学习笔记 p5-13
笔记·分布式·学习
Qinn-2 小时前
【学习笔记】Trae IDE学习笔记(更新中)
学习
Yeh2020582 小时前
2月11日笔记
笔记
凉、介2 小时前
关于家用路由器的一些知识
网络·笔记·学习·智能路由器