一、引言

在嵌入式系统开发中,USB 主机驱动的实现是一个常见且重要的任务。华大的 HC32F460 系列芯片具有丰富的外设资源,为我们实现 USB 主机驱动提供了强大的硬件支持。本文将详细介绍如何使用 HC32F460 芯片实现 USB 主机驱动,配套的从机是一个复合设备 CDC(通信设备类)+ MSC(大容量存储设备类),这个配套设备可以使用官方例程+开发板。

非常重要:需要使用小华半导体ddl3.3的版本,否则会调试到怀疑人生!别问我是怎么知道的。。。

二、开发环境准备

2.1 硬件

  • HC32F460 开发板:作为 USB 主机,我们将使用该开发板来实现 USB 主机驱动。
  • CDC+MSC 复合设备:作为 USB 从机,与 HC32F460 开发板进行通信。

2.2 软件

  • 开发工具:选择适合 HC32F460 芯片的开发工具,如 Keil MDK 等。
  • 华大芯片 SDK:包含了 HC32F460 芯片的驱动库和示例代码,我们可以基于此进行开发。

三、代码实现

3.1 配置文件 hc32f4xx_conf.h

该文件用于配置芯片的各个模块是否启用。在实现 USB 主机驱动时,我们需要确保相关模块被正确启用。

#define LL_ICG_ENABLE                               (DDL_ON)
#define LL_UTILITY_ENABLE                           (DDL_ON)
#define LL_PRINT_ENABLE                             (DDL_ON)
#define LL_CLK_ENABLE                               (DDL_ON)
#define LL_GPIO_ENABLE                              (DDL_ON)
#define LL_I2C_ENABLE                               (DDL_ON)
#define LL_INTERRUPTS_ENABLE                        (DDL_ON)
#define LL_KEYSCAN_ENABLE                           (DDL_ON)
#define LL_SRAM_ENABLE                              (DDL_ON)
#define LL_USART_ENABLE                             (DDL_ON)
#define LL_USB_ENABLE                               (DDL_ON)

3.2 系统初始化 system_hc32f460.c

该文件中的 SystemInit 函数用于初始化系统,更新系统时钟。

void SystemInit(void)
{
    /* FPU settings */
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
    SCB->CPACR |= ((3UL << 20) | (3UL << 22)); /* set CP10 and CP11 Full Access */
#endif
    SystemCoreClockUpdate();
#if defined (ROM_EXT_QSPI)
    SystemInit_QspiMem();
#endif /* ROM_EXT_QSPI */
    /* Configure the Vector Table relocation */
    SCB->VTOR = VECT_TAB_OFFSET;    /* Vector Table Relocation */
}

void SystemCoreClockUpdate(void)
{
    uint8_t u8SysClkSrc;
    uint32_t plln;
    uint32_t pllp;
    uint32_t pllm;
    uint32_t u32PllSrcFreq;

    /* Select proper HRC_VALUE according to ICG1.HRCFREQSEL bit */
    if (1UL == (HRC_FREQ_MON() & 1UL)) {
        HRC_VALUE = HRC_16MHz_VALUE;
    } else {
        HRC_VALUE = HRC_20MHz_VALUE;
    }
    u8SysClkSrc = CM_CMU->CKSWR & CMU_CKSWR_CKSW;
    switch (u8SysClkSrc) {
        case 0x00U: /* use internal high speed RC */
            SystemCoreClock = HRC_VALUE;
            break;
        case 0x01U: /* use internal middle speed RC */
            SystemCoreClock = MRC_VALUE;
            break;
        case 0x02U: /* use internal low speed RC */
            SystemCoreClock = LRC_VALUE;
            break;
        case 0x03U: /* use external high speed OSC */
            SystemCoreClock = XTAL_VALUE;
            break;
        case 0x04U: /* use external low speed OSC */
            SystemCoreClock = XTAL32_VALUE;
            break;
        case 0x05U:  /* use MPLL */
            /* PLLCLK = ((pllsrc / pllm) * plln) / pllp */
            plln = (CM_CMU->PLLCFGR & CMU_PLLCFGR_MPLLN) >> CMU_PLLCFGR_MPLLN_POS;
            pllp = (CM_CMU->PLLCFGR & CMU_PLLCFGR_MPLLP) >> CMU_PLLCFGR_MPLLP_POS;
            pllm = (CM_CMU->PLLCFGR & CMU_PLLCFGR_MPLLM) >> CMU_PLLCFGR_MPLLM_POS;
            if (0UL == (CM_CMU->PLLCFGR & CMU_PLLCFGR_PLLSRC)) {  /* use external highspeed OSC as PLL source */
                u32PllSrcFreq = XTAL_VALUE;
            } else {                                    /* use internal high RC as PLL source */
                u32PllSrcFreq = HRC_VALUE;
            }
            SystemCoreClock = u32PllSrcFreq / (pllm + 1UL) * (plln + 1UL) / (pllp + 1UL);
            break;
        default:
            break;
    }
}

3.3 USB 主机驱动实现

虽然代码中未给出完整的 USB 主机驱动代码,但我们可以根据 SDK 中的示例进行开发。一般来说,USB 主机驱动的实现步骤如下:

  1. 初始化 USB 主机控制器:配置 USB 主机控制器的相关寄存器,使其能够正常工作。
  2. 检测 USB 设备连接:通过轮询或中断的方式检测 USB 设备的连接和断开。
  3. 枚举 USB 设备:当检测到 USB 设备连接后,对设备进行枚举,获取设备的描述符信息,确定设备的类型(CDC 或 MSC)。
  4. 与 USB 设备通信:根据设备类型,实现相应的通信协议,如 CDC 通信协议和 MSC 通信协议。

以下是一个简单的 USB 主机初始化示例:

void USB_Host_Init(void)
{
    // 初始化 USB 主机控制器
    // 配置相关寄存器
    // 使能 USB 主机控制器
}

void USB_Host_Detect_Device(void)
{
    // 检测 USB 设备连接
    if (USB_DEVICE_CONNECTED) {
        // 设备连接,进行枚举
        USB_Host_Enumerate_Device();
    } else if (USB_DEVICE_DISCONNECTED) {
        // 设备断开,进行相应处理
    }
}

void USB_Host_Enumerate_Device(void)
{
    // 发送枚举请求,获取设备描述符
    // 解析设备描述符,确定设备类型
    if (DEVICE_TYPE_CDC) {
        // 处理 CDC 设备
    } else if (DEVICE_TYPE_MSC) {
        // 处理 MSC 设备
    }
}

例程中有相关对应的代码:

主机初始化的代码主要涉及 usb_host_init 函数,其定义位于 USBhostMSC-fat32-CDC/midwares/hc32/usb/usb_host_lib/host_core/usb_host_core.c 文件中。以下是该函数的代码:

/**
 * @brief  initialization for the host application
 * @param  [in] pdev                device instance
 * @param  [in] pstcPortIdentify    usb core and phy select
 * @param  [in] phost               host state set
 * @param  [in] class_cbk           the call back function for the class application
 * @param  [in] user_cbk            the call back function for user
 * @retval None
 */
void usb_host_init(usb_core_instance *pdev,
                   stc_usb_port_identify *pstcPortIdentify,
                   USBH_HOST *phost,
                   usb_host_class_callback_func *class_cbk,
                   usb_host_user_callback_func *user_cbk)
{
    //usb_bsp_init(pdev, pstcPortIdentify);
    usb_host_deinit(pdev, phost);
    phost->class_callbk = class_cbk;
    phost->user_callbk  = user_cbk;
    host_driver_init(pdev, pstcPortIdentify);
    phost->user_callbk->huser_init();
    usb_bsp_nvicconfig(pdev);
}

代码解释

  1. usb_host_deinit(pdev, phost);:在初始化之前,先调用 usb_host_deinit 函数对主机应用进行反初始化,将主机的状态和参数恢复到初始状态。
  2. phost->class_callbk = class_cbk; 和 phost->user_callbk = user_cbk;:将传入的类应用回调函数和用户回调函数赋值给主机状态结构体 phost 中的相应成员,以便在后续的操作中调用这些回调函数。
  3. host_driver_init(pdev, pstcPortIdentify);:调用 host_driver_init 函数对主机驱动进行初始化,配置 USB 主机控制器的相关寄存器和参数。
  4. phost->user_callbk->huser_init();:调用用户回调函数 huser_init,允许用户在主机初始化完成后执行一些自定义的初始化操作。
  5. usb_bsp_nvicconfig(pdev);:调用 usb_bsp_nvicconfig 函数对 USB 主机的中断向量进行配置,使能相应的中断。

调用示例

在 USBhostMSC-fat32-CDC/USBhostCDCMSC20250304/source/main.c 文件中,有对 usb_host_init 函数的调用示例:

int main(void)
{
    stc_usb_port_identify stcPortIdentify;
    stcPortIdentify.u8CoreID = USBFS_CORE_ID;
    usb_bsp_init(); // 硬件初始化,可能包含一些底层硬件的初始化操作
    usb_host_init(&usb_app_instance, &stcPortIdentify, &usb_app_host, &USBH_CDC_MSC_cb, &USR_cb);
    for (;;) {
        usb_host_mainprocess(&usb_app_instance, &stcPortIdentify, &usb_app_host);
        // 其他操作
    }
}

在 main 函数中,首先定义了一个 stc_usb_port_identify 结构体变量 stcPortIdentify,并设置其 u8CoreID 成员为 USBFS_CORE_ID。然后调用 usb_bsp_init 函数进行硬件初始化,接着调用 usb_host_init 函数对 USB 主机进行初始化。最后,在一个无限循环中调用 usb_host_mainprocess 函数处理 USB 主机的主要操作。

在USB的驱动中,一般使用驱动模板即可,模板需要实现的无非就是四个函数。
 

usb_host_class_callback_func  USBH_CDC_MSC_cb = {
    &usb_host_cdc_msc_itfinit,
    &usb_host_cdc_msc_itfdeinit,
    &usb_host_cdc_msc_classreq,
    &usb_host_cdc_msc_process,
};

 分别表述枚举完成之后的初始化、反初始化、请求和处理过程。初始化就是绑定端点和打开通道,请求根据不同的从机有不同的请求,CDC通信中请求注意是设置波特率;msc没有请求。主要的处理过程就是用户业务过程,CDC是实现数据的首发;msc是实现一个u盘的处理。

3.4 CDC 通信实现

CDC 设备主要用于实现串口通信功能。在实现 CDC 通信时,我们需要实现以下几个关键步骤:

  1. 打开 CDC 设备:通过 USB 主机与 CDC 设备建立通信连接。
  2. 发送和接收数据:使用 USB 主机向 CDC 设备发送数据,并接收 CDC 设备返回的数据。
  3. 关闭 CDC 设备:当通信结束后,关闭 CDC 设备。
void CDC_Open(void)
{
    // 打开 CDC 设备
    // 配置相关参数
}

void CDC_Send_Data(uint8_t *data, uint32_t length)
{
    // 向 CDC 设备发送数据
    // 使用 USB 主机发送数据
}

void CDC_Receive_Data(uint8_t *data, uint32_t length)
{
    // 从 CDC 设备接收数据
    // 使用 USB 主机接收数据
}

void CDC_Close(void)
{
    // 关闭 CDC 设备
    // 释放相关资源
}

 上述部分直接抄写CDC的主机例程即可。

3.5 MSC 通信实现

MSC 设备主要用于实现大容量存储功能。在实现 MSC 通信时,我们需要实现以下几个关键步骤:

  1. 初始化 MSC 设备:通过 USB 主机与 MSC 设备建立通信连接,并初始化设备。
  2. 读取和写入数据:使用 USB 主机向 MSC 设备读取和写入数据。
  3. 关闭 MSC 设备:当通信结束后,关闭 MSC 设备。
void MSC_Init(void)
{
    // 初始化 MSC 设备
    // 发送初始化命令
}

void MSC_Read_Data(uint32_t address, uint8_t *data, uint32_t length)
{
    // 从 MSC 设备读取数据
    // 发送读取命令
    // 接收数据
}

void MSC_Write_Data(uint32_t address, uint8_t *data, uint32_t length)
{
    // 向 MSC 设备写入数据
    // 发送写入命令
    // 发送数据
}

void MSC_Close(void)
{
    // 关闭 MSC 设备
    // 释放相关资源
}

上述部分直接抄写MSC的主机例程即可。 

四、总结

通过以上步骤,我们可以使用华大的 HC32F460 芯片实现 USB 主机驱动,配套的从机是一个复合设备 CDC+MSC。在实际开发中,我们需要根据具体的需求和硬件平台进行适当的调整和优化。同时,我们还需要注意 USB 通信协议的细节,确保通信的稳定性和可靠性。

希望本文对你有所帮助,祝你在嵌入式开发中取得成功!

最后附一张我的工作场景:

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐