STM32片上驱动详解:USB驱动实践指南

一、USB概述

USB全称Universal Serial Bus(通用串行总线);

USB是一种支持热插拔的总线接口,使用差分线(D-和D+)来传输数据,USB支持两种供电模式:总线供电和自供电,总线供电就是由USB接口为外部设备供电,在USB2.0下,总线供电最大可以提供500mA的电流。

  1. USB2.0:由Inter、IBM、Microsoft等公司提出并发布;
  2. 两个版本:Full-Speed全速(FS)和High-Speed高速(HS),USB2.0 FS的速度为12Mbps,USB2.0 HS速度为480Mbps;
  3. 目前大多数MCU以及低端Cortex-A芯片配置的都是USB2.0接口;

USB电气特性:

  1. USB A插头从左到右线序依次为1,2,3,4,第1根线为VBUS,电压为5V,第2根线为D-,第3根线为D+,第4根线为GND(USB采用差分信号来传输数据,因此有D-和D+两根差分信号线);
  2. USB A插头的1和4这两个触点比较长,2和3这两个触点比较短,当插入USB的时候会先供电,然后再接通数据线,拔出的时候先断开数据线,然后再断开电源线;
  3. Mini USB插头有5个触点,线序从左往右依次是1~5,第1根线为VCC(5V),第2根线为D-,第3根线为D+,第4根线为ID,第5根线为GND,ID线用于实现OTG功能,通过ID线来判断当前连接的是主设备(HOST)还是从设备(SLAVE):

ID=1:OTG设备工作在从机模式;

ID=0:OTG设备工作在主机模式,比如U盘会将ID线拉低,上,需要插到另一端的USB A插座上。

USB拓补结构:

  1. USB是主从结构的,也就是分为主机和从机两部分,一般主机叫做Host,从机叫做Device;
  2. 主机就是提供USB A插座来连接外部的设备,可使用USB集线器(USB HUB)拓展USB接口,可以对原生的USB口数量进行扩展,但是不能对原生USB口的带宽进行扩展,比如个原生USB口都是USB2.0的,带宽最大为480Mbps,因此接到下面的所有USB设备总带宽最大为480Mbps;
  3. USB只能主机与设备之间进行数据通信,USB主机与主机、设备与设备之间是不能通信的;
  4. 在一个USB系统中,仅有一个USB主机,但是可以有多个USB设备,包括USB功能设备和USB HUB,最多支持127个设备,一个USB主控制器支持128个地址,地址0是默认地址,只有在设备枚举的时候才会使用,地址0不会分配给任何一个设备,所以一个USB主控制器最多可以分配127个地址。

  1. STM32系列包括支持USB 2.0全速(12 Mbps)和高速(480 Mbps)的微控制器;
  2. 全速USB适用于大多数常见应用,高速USB适用于需要更高数据传输速率的应用。

二、USB虚拟串口

CDC (Communication Device Class):用于创建虚拟串行端口,适合用于串口通信的替代。

1、CubeMX驱动生成

使能USB_FS:

配置USB_DEVICE:

配置时钟:

2、使用USB设备

确保HAL库hal_conf.h功能模块使能(CubeMX会自动解除注释):

#define HAL_PCD_MODULE_ENABLED

注释cubemx/Inc/usb_device.h报错信息:

//#include "usbd_def.h"

添加drivers/drv_usbd.c宏定义:

#define EP_MPS_64                   0U

添加usbd_conf.c硬件驱动到board.c:

void HAL_PCD_MspInit(PCD_HandleTypeDef* pcdHandle)

{

    GPIO_InitTypeDef GPIO_InitStruct = {0};

    if(pcdHandle->Instance==USB_OTG_FS) {

        /* USER CODE BEGIN USB_OTG_FS_MspInit 0 */

        

        /* USER CODE END USB_OTG_FS_MspInit 0 */

        

        __HAL_RCC_GPIOA_CLK_ENABLE();

        /**USB_OTG_FS GPIO Configuration

        PA11     ——> USB_OTG_FS_DM

        PA12     ——> USB_OTG_FS_DP

        */

        GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12;

        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;

        GPIO_InitStruct.Pull = GPIO_NOPULL;

        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;

        GPIO_InitStruct.Alternate = GPIO_AF10_OTG_FS;

        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

        

        /* Peripheral clock enable */

        __HAL_RCC_USB_OTG_FS_CLK_ENABLE();

        

        /* Peripheral interrupt init */

        HAL_NVIC_SetPriority(OTG_FS_IRQn, 0, 0);

        HAL_NVIC_EnableIRQ(OTG_FS_IRQn);

        /* USER CODE BEGIN USB_OTG_FS_MspInit 1 */

        

        /* USER CODE END USB_OTG_FS_MspInit 1 */

    }

}

修改board.h:

#define BSP_USING_USBDEVICE

list device可以看到usbd和vcom就大功告成:

3、示例代码
#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

static struct rt_semaphore rx_sem;
static rt_device_t serial;

static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
    // 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量
    rt_sem_release(&rx_sem);
    return RT_EOK;
}

static void serial_thread_entry(void *parameter)
{
    char ch;
    LOG_D("uart_rx_thread_entry runing..\n");
    while (1) {
        // 从串口读取一个字节的数据,没有读取到则等待接收信号量
        while (rt_device_read(serial, -1, &ch, 1) != 1) {
            // 阻塞等待接收信号量,等到信号量后再次读取数据
            rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
        }
        rt_kprintf("%c",ch);
        // 读取到的数据通过串口错位输出
        rt_device_write(serial, 0, &ch, 1);
    }
}

static int serial_init()
{
    rt_err_t ret = RT_EOK;

    // 查找串口设备
    serial = rt_device_find("vcom");
    if (serial  == RT_NULL) {
        rt_kprintf("can't find vcom device!\n");
        return RT_ERROR;
    }
    // 初始化串口设备
    ret = rt_device_init(serial);
    if (serial  == RT_NULL) {
        rt_kprintf("can't initialize vcom device!\n");
        return RT_ERROR;
    }

    // 打开设备,可读写,中断接收
    ret = rt_device_open(serial, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX);

    // 初始化信号量
    rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);

    // 设置接收回调函数
    rt_device_set_rx_indicate(serial, uart_input);

    // 创建接收线程
    rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
    if (thread == RT_NULL) {
        LOG_E("rt_thread_create failed...\n");
    }
    rt_thread_startup(thread);

    return RT_EOK;
}

int main(void)
{
    serial_init();

    while (1) {
        LOG_D("Hello RT-Thread!");
        rt_thread_mdelay(1000);
    }

    return RT_EOK;
}

作者:Xiangfu DING

物联沃分享整理
物联沃-IOTWORD物联网 » STM32片上驱动详解:USB驱动实践指南

发表回复