USB MStorage指南

1. 介绍

SiFli USB控制器支持DEVICE、HOST等设备模式,其中DEVICE模式可支持CDC、MSTORAGE、HID、WINUSB等功能。本文主要说明USB作为MSTORAGE设备的使用流程:

  • 工作模式:作为DEVICE的MSTORAGE设备时,在PC上以可读写U盘形式呈现

  • 核心功能:PC可对U盘内文件进行增删改操作,拔出后修改会同步保留在HDK文件系统对应分区

2. USB配置

USB配置包括PINMUX引脚配置和menuconfig配置,需注意:

  • 时钟需运行在60MHz

  • 仅支持fatfs文件系统,暂不支持LittleFs

2.1 PINMUX引脚配置

需将USB引脚配置为IO模式,代码如下:

#if defined(BSP_USING_USBD) || defined(BSP_USING_USBH)
    HAL_PIN_Set_Analog(PAD_PA01, 1);
    HAL_PIN_Set_Analog(PAD_PA03, 1);
#endif

特殊处理(563hdk)
563hdk的USB引脚与uart1引脚复用,而默认配置中uart1用于HCPU日志打印。若需正常使用USB,需修改如下配置:

  • 关闭“Enable UART1”

  • 开启“Enable UART4”和“Enable UART4 RX DMA”

  • 将HCPU日志打印切换为uart4(配置项:“the device name for console -> uart4”)

相关配置截图:

../_images/usb_uart_1.png ../_images/usb_uart_2.png

3. USB功能代码流程

3.1 核心流程

  1. 插入检测: callback(app_usb_function(USB_CONTROL_ENABLED)) → usb_start() → enabled_usb() → USB 驱动 → PC 挂载

  2. 拔出检测: callback(app_usb_function(USB_CONTROL_DISABLED)) → usb_stop() → disabled_usb() → USB 驱动 → HDK 卸载 → HDK 重新挂载

3.2 关键接口实现

3.2.1 核心控制函数app_usb_function

void app_usb_function(void *on_off)
{
    struct rt_device *usb_device;
    uint8_t usb_stat  = *(uint8_t *)on_off;
    LOG_I("%s %d", __func__, usb_stat);
    if (USB_CONTROL_ENABLED == usb_stat)
    {
#ifdef BSP_PM_FREQ_SCALING
        rt_pm_hw_device_start();                    //关闭自动降频
#endif
        usb_start();
    }
    if (USB_CONTROL_DISABLED == usb_stat)
    {
        usb_stop();
        usb_ota_to_msg_app();                       //上报OTA升级包事件
        usb_to_msg_gui(NULL, 0, USB_GUI_MSG_ID);    //上报USB事件ID
#ifdef BSP_PM_FREQ_SCALING
        rt_pm_hw_device_stop();                     //打开自动降频
#endif
    }
}

3.2.2 插入 / 拔出检测方式

USB检测可以根据使用情况分为USB检测引脚进行识别与USB充电接口识别;

  1. USB 检测引脚识别

//usb 检测引脚回调函数
static int irq_usb_service_callback(data_callback_arg_t *arg)
{
    if (MSG_SERVICE_DATA_NTF_IND == arg->msg_id)
    {
        pin_common_msg_t pin_msg;
        RT_ASSERT(arg != NULL);
        RT_ASSERT(sizeof(pin_common_msg_t) == arg->data_len);
        memcpy(&pin_msg, arg->data, arg->data_len);
        if (pin_msg.id == INSERT_DETE_USB_PIN)
        {
            rt_usb_irq_pin_enable(0);                                       //关闭中断
            rt_thread_mdelay(200);                                          //usb检测pin防抖延时
            int read_pin = rt_pin_read(INSERT_DETE_USB_PIN);
            if (read_pin == 1)
            {
                if (USB_CONTROL_DISABLED == usb_get_irq_state())
                {
                    LOG_I("%s USB_CONTROL_PIN_IN", __func__);
                    usb_set_irq_state(USB_CONTROL_ENABLED);
                    app_usb_function(&usb_pin_state);                       //调用USB协议
                }
            }
            else if (USB_CONTROL_ENABLED == usb_get_irq_state())
            {
                LOG_I("%s USB_CONTROL_PIN_OUT", __func__);
                usb_set_irq_state(USB_CONTROL_DISABLED);
                app_usb_function(&usb_pin_state);
            }
            rt_usb_irq_pin_enable(1);
        }
    }
    .......
}
  1. USB 充电接口识别

  • 收到充电事件:调用app_usb_function(USB_CONTROL_ENABLED)

  • 收到充电移除事件:调用app_usb_function(USB_CONTROL_DISABLED)

在USB使用时频率需要一直保持在60MHz运行,因此系统不能降频也不能进行睡眠,所以在检测到USB插入时候系统需要进入IDLE模式再开启USB使能进行USB协议检测,只有当USB协议检测成功后PC才能识别到当前U盘

3.2.3 USB 启动与停止

  • 启动 USB(usb_start)

void usb_start(void)
{
    usb_timer_start();                                  //开启定时器;500ms检测一次USB协议
    if (!usb_flag)
    {
        rt_kprintf("%s rt_pm_request\n", __func__);
        rt_pm_request(PM_SLEEP_MODE_IDLE);              //系统进入IDLE模式
        usb_flag = 1;
    }
    enabled_usb();                                      //使能USB协议
}

在拔出USB后需要对将加载到PC上的分区进行卸载后再重新再HDK上进行加载;

  • 停止 USB(usb_stop)

void usb_stop(void)
{
    udevice_t usb_device;
    disabled_usb();
    if (USB_IN == device_status_get(USB_CONNECT_STATUS))    //成功在PC上加载过分区
    {
        usb_unmount_fs();                                   //先卸载分区
        usb_mount_fs();                                     //重新加载分区
    }
    if (usb_flag)
    {
#ifdef RT_USING_PM
        LOG_I("%s rt_pm_release", __func__);
        rt_pm_release(PM_SLEEP_MODE_IDLE);                  //退出IDLE
#endif
        usb_flag = 0;
    }
    device_status_set(USB_CONNECT_STATUS, USB_OUT);         //设置USB模块状态
    usb_device = rt_usbd_find_device(usb_dcd);
    RT_ASSERT(usb_device);
    usb_device->state = USB_STATE_NOTATTACHED;              // 重置USB协议状态
}
  • 使能 USB(enabled_usb)

static void enabled_usb(void)
{
    struct rt_device *usb_device;
    usb_device = rt_device_find("usb_reg");
    if (usb_device)
    rt_device_control(usb_device, RT_DEVICE_OFLAG_OPEN, NULL);
}
  • 关闭 USB(disabled_usb)

static void disabled_usb(void)
{
    struct rt_device *usb_device;
    usb_device = rt_device_find("usb_reg");
    if (usb_device)
        rt_device_control(usb_device, RT_DEVICE_OFLAG_CLOSE, NULL);
}

3.2.4 USB 协议检测

通过定时器超时函数检测 USB 协议是否识别成功(5 秒内未识别则关闭 USB):

/**
功能:定时检测USB协议
使用:开了一个定时器去定时检测USB协议是否识别成功
*/
static udcd_t               usb_dcd;
int app_usb_init(void)
{
    usb_dcd = (udcd_t)rt_device_find("usbd");
    RT_ASSERT(usb_dcd);
}

static uint8_t usb_handler(void)
{
    static uint8_t usb_protocol_flag = 0;
    uint8_t usb_intput;
    udevice_t usb_device;
    usb_device = rt_usbd_find_device(usb_dcd);                  //usb_dcd是一个全局变量的USB设备指针,在初始化时去find该设备并赋值给usb_dcd
    RT_ASSERT(usb_device);
    rt_kprintf("%s,state=%d\n", __func__, usb_device->state);
    if (USB_STATE_CONFIGURED == usb_device->state)              //USB协议识别成功
    {
        device_status_set(USB_CONNECT_STATUS, USB_IN);          //设置USB 的状态
        usb_protocol_flag = 0;
        usb_to_msg_gui(NULL, 0, USB_GUI_MSG_ID);                //上报USB识别成功的状态
        usb_timer_stop();                                       //USB协议识别成功就关闭当前定时器
    }
    else                                                        //USB没有检测到协议
    {
        usb_protocol_flag ++;
        if (10 == usb_protocol_flag)                            //5s内没有识别到协议
        {
            usb_stop();                                         //关闭USB模块
            usb_timer_stop();                                   //关闭当前定时器
            usb_protocol_flag = 0;
        }
    }
    return 0;
}

3.2.5 USB 功耗控制(拔出后)

在USB拔出后为了节约功耗需要将USB功能禁用并pinmux配置为如下

void BSP_USB_Power_Down(void)
{
#if defined(BSP_USING_USBD) && defined(BSP_USING_USBH)
    HAL_PIN_Set(PAD_PA01, GPIO_A1, PIN_PULLUP, 1);
    HAL_PIN_Set(PAD_PA03, GPIO_A3, PIN_PULLUP, 1);
#endif
}

4. USB OTA升级

通过 USB 实现 OTA 升级(支持资源差分、代码差分、全量升级)。具体实现为在通过usb数据线在pc上挂载系统指定的分区后将需要升级的文件放入指定的文件夹中,在拔出USB后会直接检测指定的文件夹里是否含有文件,如果有就执行系统升级,并上报GUI进行界面显示。

使用USB作为OTA的流程为:拔出USB callback(app_usb_function(USB_CONTROL_DISABLED)) → usb_read_ota_dir(检测是否有OTA文件) → usb_to_msg_gui(向UI上报OTA事件) → usb_ota_all_file(执行升级)

4.1 升级目录与配置

####4.1.1 升级目录定义

/**
 * @brief  OTA check ID
 */
enum
{
    USB_OTA_CHECK_ROOT      = 0X01,
    USB_OTA_CHECK_DYN       = 0X02,
    USB_OTA_CHECK_MUSIC     = 0X04,
    USB_OTA_CHECK_UDISK     = 0X08,
    USB_OTA_CHECK_MISC      = 0X10,
    USB_OTA_CHECK_HCPU      = 0X20,

    USB_OTA_MOVING_IDLE     = 0XFF,
    USB_OTA_MOVING_ING      = 0xC0,
    USB_OTA_MOVING_SUCC     = 0x3F,
};

/**
 * @brief  OTA Upgrade Directory Contains resources and code
 */
#define USB_DIFF_RES_PATH                   "/udisk/diff_res"
#define USB_DIFF_CODE_PATH                  "/udisk/diff_code"
#define USB_DIFF_RES_ROOT_PATH              "/udisk/diff_res/root"          /**<root Resource upgrade directory           */
#define USB_DIFF_RES_DYN_PATH               "/udisk/diff_res/dyn"           /**<dyn Resource upgrade directory           */
#define USB_DIFF_RES_MUSIC_PATH             "/udisk/diff_res/music"         /**<music Resource upgrade directory           */
#define USB_DIFF_RES_MISC_PATH              "/udisk/diff_res/misc"          /**<misc Resource upgrade directory           */
#define USB_DIFF_RES_UDISK_PATH             "/udisk/diff_res/udisk"         /**<udisk Resource upgrade directory           */
#define USB_DIFF_CODE_HCPU_PATH             "/udisk/diff_code/hcpu"         /**<hcpu code upgrade directory           */

4.1.2 升级目录与 ID 映射

/**
 * @brief  Directory and corresponding ID for USB upgrade
 */
struct usb_diff_msg
{
    char  *path;                                                            /**升级文件的目录*/
    char  *dev_name;                                                        /**升级文件的device设备名*/
    uint16_t id;                                                            /**check id*/
};
static struct usb_diff_msg usb_ota_msg[] =
{
    {USB_DIFF_RES_ROOT_PATH,        "root",          USB_OTA_CHECK_ROOT},
    {USB_DIFF_RES_DYN_PATH,         "dyn",           USB_OTA_CHECK_DYN},
    {USB_DIFF_RES_MUSIC_PATH,       "music",         USB_OTA_CHECK_MUSIC},
    {USB_DIFF_RES_UDISK_PATH,       "udisk",         USB_OTA_CHECK_UDISK},
    {USB_DIFF_RES_MISC_PATH,        "misc",          USB_OTA_CHECK_MISC},

    {USB_DIFF_CODE_HCPU_PATH,       "code_pang",     USB_OTA_CHECK_HCPU},
};

4.2 升级核心流程

OTA升级流程如下:

  • 系统资源升级:发现有升级文件 → 使用读取文件系统的方式读取需要升级的文件 → 将读取的文件写入到相应的资源地址 → 写入kvdb标志用于开机检测 → 删除文件系统文件 → 重启

  • 代码升级:发现有升级文件 → 使用读取文件系统的方式读取需要升级的文件 → 将读取的文件写入到emmc一段空白地址处 → 写入env标志位用于OTAmanager执行 → 写入kvdb标志用于开机检测 → 删除文件系统文件 → 重启

4.2.1 检测升级文件并上报

/**
功能:向UI发送消息接口
*/
void usb_ota_to_msg_app(void)
{
    int ota_file_page_num = 0, file_len = 0, page_num = 0;
    for (int i = 0; i < USB_OTAD_PATH_FILE_NUM; i ++)//遍历udisk上的所有文件夹
    {
        file_len = usb_read_ota_dir(usb_ota_msg[i].path, NULL); //读取指定目录是都有文件
        page_num = file_len / USB_OTA_PACKAGE_SIZE;             //计算升级的包数 512字节一包
        if (file_len % USB_OTA_PACKAGE_SIZE) page_num++;        //计算余数
        ota_file_page_num += page_num; /*Calculate the total number of packages to upgrade*/
    }
    if (ota_file_page_num)
    {
        usb_to_msg_gui(NULL, ota_file_page_num, USB_GUI_OTA_ID);/*Send the total number of packages that need to be upgraded to the GUI*/
        LOG_I("%s page num %d", __func__, ota_file_page_num);
        usb_ota_all_file(usb_ota_msg, ota_file_page_num);/*Execute USB upgrade resources and code*/
    }
}

4.2.2 执行全量升级(usb_ota_all_file)

/*
    功能:ota所有文件
    参数:ota_msg->所有的升级目录信息;ota_page_max:总包数(512一个包)
*/
void usb_ota_all_file(struct usb_diff_msg ota_msg[], uint32_t ota_page_max)
{
    uint32_t bin_size = 0;
    ota_page_count = 0;
    usb_ota_write_flag(0x00);                                               //将check id 全部设置为0
    for (int i = 0; i < USB_OTAD_PATH_FILE_NUM; i ++)
    {
        LOG_I("%s,path=%s", __func__, ota_msg[i].path);
        bin_size = usb_ota_file_transfer(ota_msg[i], ota_page_max);         //执行单个文件的升级
    }
    usb_ota_write_flag(usb_ota_read_flag() >> 0x2);                         //写入升级完成的标志 00xx xxxx:(7,8位:00升级完成;11升级中)
    usb_ota_jump(usb_ota_get_type(), bin_size);                             //重启跳转
}
/*
    功能:ota文件搬移与标志位写如
    参数:ota_msg:需要升级的文件信息;page_max:总包数(512一个包)
    返回:如果有执行代码(HCPU)的升级就返回该文件大小,没有就返回0.
*/
/*Performing an upgrade of a single file*/
static uint32_t usb_ota_file_transfer(struct usb_diff_msg ota_msg, uint32_t page_max)
{
    uint32_t size = 0, ota_hcpu_size = 0;
    uint8_t usb_check = 0;
    char file_name[USB_DEVICE_NAME_LEN] = {0};
    memset(file_name, 0x00, USB_DEVICE_NAME_LEN);
    usb_check = usb_ota_read_flag();                                                //获取check ID
    size = usb_read_ota_dir(ota_msg.path, file_name);
    if (size)
    {
        LOG_I("%s ota_msg.path=%s,name=%s", __func__, ota_msg.path, file_name);
        usb_check |= USB_OTA_MOVING_ING;/*Upgrading*/
        usb_ota_write_flag(usb_check);/*Write upgrade flag*/
        if (RT_EOK == usb_ota_moving_file(ota_msg, file_name, page_max))            //进行文件的搬移
        {
            LOG_I("%s moving succ !!!", __func__);
            usb_check |= ota_msg.id;/*file upgrad succ*/
            usb_ota_write_flag(usb_check);/*Write upgrade flag*/
            if (0 == strcmp(ota_msg.dev_name, "code_pang"))
            {
                usb_ota_set_type(USB_OTA_TYPE_HCPU);
                ota_hcpu_size = size;
            }
            else usb_ota_set_type(USB_OTA_TYPE_RES);
        }
        else
        {
            LOG_I("%s moving fail !!!", __func__);
            usb_check |= (!ota_msg.id);/*file upgrad fail*/
            usb_ota_write_flag(usb_check);/*Write upgrade flag*/
        }
    }
    else
    {
        usb_check |= ota_msg.id;
        usb_ota_write_flag(usb_check);/*Write upgrade flag*/
    }
    return ota_hcpu_size;
}

4.2.4 重启跳转(usb_ota_jump)

/*
    功能:跳转以重启
    参数:flag:升级的类型;len:执行代码升级的文件大小
*/
void  usb_ota_jump(uint32_t flag, uint32_t len)
{
    if (flag == USB_OTA_TYPE_HCPU)
    {
        /*jump to ota manager code*/
        LOG_I("%s,USB_OTA_CODE_HCPU drv_reboot,file_size=%d", __func__, len);
        dfu_ctrl_env_t *env = dfu_ctrl_get_env();
        env->prog.state = DFU_CTRL_USB_INSTALL;                                 //写入OTA manager的标志
        env->prog.FW_version = len;                                             //HCPU 代码大小
        dfu_ctrl_update_prog_info(env);                                         //保存env
        drv_reboot();                                                           //重启
    }
    else if (flag == USB_OTA_TYPE_RES)
    {
        /*Just restart the resource file*/
        LOG_I("%s,USB_OTA_RES drv_reboot", __func__);
        drv_reboot();
    }
}

4.2.5 开机检测升级结果

/**
 * check ID:是由一个uint8_t的数以位作为每个文件的标志组成;
 * check ID:uint8_t ID = 0x3f ;   1111 1111 :1-6位依次为文件 ROOT DYN MUSIC MISC UDISK HCPU的升级成功标志 1-升级成功 0-升级失败;7-8位 11-升级中;00升级完成,
 * 在usb_ota_file_transfer()接口中没有升级此文件时候也会将该文件的标志置为1;因此0x3f是升级完成,在升级完成后会将check ID置为0xff。
 * 
*/
static void dfs_check_res_integrity(void)
{
    uint8_t usb_ota_stater = 0;
    usb_ota_stater = usb_ota_read_flag();
    LOG_I("%s usb_ota_stater=%p", __func__, usb_ota_stater);
    switch (usb_ota_stater)
    {
    case USB_OTA_MOVING_IDLE:
    {
        LOG_I("%s IDLE", __func__);
    }
    break;
    case USB_OTA_MOVING_SUCC:                                       //升级成功
    {
        LOG_I("%s Resource updated !", __func__);
        usb_ota_write_flag(USB_OTA_MOVING_IDLE);                    //写入0xff
    }
    break;
    default :
    {
        LOG_I("%s Upgrade failed, please upgrade again !", __func__);
        usb_ota_to_msg_app();                                       //为其他值视为升级失败,就重新进入升级流程
    }
    break;

    }
}

4.3 OTA Manager(代码升级专用)

仅当升级 HCPU 代码时进入,负责将 emmc 中的升级文件写入 APP 运行地址。(只升级资源就直接覆盖后重启即可)。

4.3.1 重置处理函数

uint8_t dfu_ctrl_reset_handler(void)
{
    dfu_ctrl_env_t *env = dfu_ctrl_get_env();
    RT_ASSERT(env->mode == DFU_CTRL_OTA_MODE);
    uint16_t state = env->prog.state;

    uint8_t status = DFU_ERR_GENERAL_ERR;
    uint8_t is_jump = 0;
    LOG_I("dfu_ctrl_reset_handler %d", state);
    // dfu_ota_bootloader_ram_run_set(DFU_RAM_STATE_UPDATE_FAIL);

    if (HAL_Get_backup(RTC_BAKCUP_OTA_FORCE_MODE) == 1)
    {
        HAL_Set_backup(RTC_BAKCUP_OTA_FORCE_MODE, 0);
        dfu_flash_addr_reset(0);
        dfu_ctrl_boot_to_user_fw();
    }
    ...........
    switch (state)
    {
 
    case DFU_CTRL_USB_INSTALL:                                  //USB升级标志
    {
#ifdef OTA_EMMC
        env->callback(DFU_APP_RESET_IND, NULL);                 //打开UI
        LOG_I("%s moving addr=%p,bin_size=%d\n", __func__, HCPU_FLASH_CODE_START_ADDR, env->prog.FW_version);
        uint8_t dfu_emmc_code_upgrade(dfu_ctrl_env_t *env, uint32_t size);
        uint8_t dfu_upgrade_result = dfu_emmc_code_upgrade(env, env->prog.FW_version);//进行code升级
        switch (dfu_upgrade_result)
        {
        case DFU_USB_FIND_FAIL:                                 //升级失败
        {
            LOG_I("%s find fail!\n", __func__);
            env->prog.state = DFU_CTRL_IDLE;                    //写入标志位 回退之前的app,重启
            env->prog.FW_version = 0;
            dfu_ctrl_update_prog_info(env);
            drv_reboot();
        }
        break;
        case DFU_USB_FAIL:                                      //在搬移过程中失败
        {
            LOG_I("%s fail!,env->prog.FW_version=%d\n", __func__, env->prog.FW_version);
            if (env->prog.FW_version == 0) env->prog.FW_version = 1;
            else if (env->prog.FW_version == 5)                 //重启超过5次就不在重启了
            {
                LOG_I("%s while(1)\n", __func__);
                while (1) {;}
            }
            else env->prog.FW_version += 1;
            dfu_ctrl_update_prog_info(env);
            drv_reboot();
        }
        break;
        }
#else
        is_jump = 1;
#endif
    }
    break;
    ...........
    return 0;
}

4.3.2 EMMC 代码升级实现

/**
 * 功能:code升级
 * 参数:env;size:文件大小
 * 返回:升级结果
*/
uint8_t dfu_emmc_code_upgrade(dfu_ctrl_env_t *env, uint32_t size)
{

    uint32_t page_num = size / DFU_EMMC_PAGE_MAX;
    uint32_t emmc_offset = 0, nor_offset = 0, end_page_size = size % DFU_EMMC_PAGE_MAX;
    if (end_page_size) page_num ++ ;

    rt_device_t dev = rt_device_find("code_pang");
    LOG_I("%s page_num=%d,dev=%p,name=%s", __func__, page_num, dev, dev->parent.name);
    env->callback(DFU_APP_DL_END_AND_INSTALL_START_IND, NULL);
    if (dev)
    {
        if (RT_EOK == rt_device_open(dev, RT_DEVICE_FLAG_RDWR))
        {
            int res = 0;
            while (page_num --)
            {
                memset(code_buf, 0x00, DFU_EMMC_PAGE_MAX);
                res = rt_device_read(dev, emmc_offset, (void *)code_buf, DFU_EMMC_PAGE_MAX / 512);//读取emmc里面的code
                if (res > 0)
                {
                    rt_flash_erase(HCPU_FLASH_CODE_START_ADDR + (nor_offset * DFU_EMMC_PAGE_MAX), DFU_EMMC_PAGE_MAX);//擦除nor
                    uint32_t wr_size = rt_flash_write(HCPU_FLASH_CODE_START_ADDR + (nor_offset * DFU_EMMC_PAGE_MAX), code_buf, DFU_EMMC_PAGE_MAX);//将代码写入nor上的app地址
                    if (wr_size != DFU_EMMC_PAGE_MAX)
                    {
                        LOG_I("%s write fail!,nor_offset=0x%x,ADDR=0x%x wr_size=%d", __func__, nor_offset, HCPU_FLASH_CODE_START_ADDR + (nor_offset * DFU_EMMC_PAGE_MAX), wr_size);
                        return DFU_USB_FAIL;
                    }
                }
                else
                {
                    rt_device_close(dev);
                    LOG_I("%s read fail!,emmc_offset=0x%x,ADDR=0x%x res=%d", __func__, emmc_offset, HCPU_FLASH_CODE_START_ADDR, res);
                    return DFU_USB_FAIL;
                }
                emmc_offset += (DFU_EMMC_PAGE_MAX / 512);
                nor_offset  += 1;
                dfu_install_progress_ind(nor_offset * DFU_EMMC_PAGE_MAX, size);//向UI发送进度
            }
        }
    }
    else
    {
        LOG_I("%s find code_pang fail!", __func__);
        rt_device_close(dev);
        return DFU_USB_FIND_FAIL;
    }
    LOG_I("%s emmc code moving sucss!", __func__);
    env->callback(DFU_APP_INSTALL_COMPLETD_IND, NULL);
    env->callback(DFU_APP_TO_USER, NULL);
    env->prog.state = DFU_CTRL_IDLE;
    env->prog.FW_state = DFU_CTRL_FW_INSTALLED;
    env->prog.FW_version = 0;
    env->is_force_update = 0;
    dfu_ctrl_update_prog_info(env);//保存状态
    dfu_ctrl_boot_to_user_fw();//重启
    return DFU_USB_EOK;
}

4.4 USB OTA使用流程

  1. 插入USB,pc上正确识别到U盘。并且能正常打开里面的目录,目录如下:

#define USB_DIFF_RES_PATH                   "/udisk/diff_res"
#define USB_DIFF_CODE_PATH                  "/udisk/diff_code"
#define USB_DIFF_RES_ROOT_PATH              "/udisk/diff_res/root"          /**<root 图库 字体 等相关资源                   */
#define USB_DIFF_RES_DYN_PATH               "/udisk/diff_res/dyn"           /**<dyn 资源升级目录                           */
#define USB_DIFF_RES_MUSIC_PATH             "/udisk/diff_res/music"         /**<music 资源升级目录                         */
#define USB_DIFF_RES_MISC_PATH              "/udisk/diff_res/misc"          /**<misc 资源升级目录                          */
#define USB_DIFF_RES_UDISK_PATH             "/udisk/diff_res/udisk"         /**<udisk 资源升级目录                         */
#define USB_DIFF_CODE_HCPU_PATH             "/udisk/diff_code/hcpu"         /**<hcpu 代码升级目录  hcpu已经包含了lcpu代码   */
  1. 将需要升级的文件放至对应的目录里面去

../_images/usb_ota_1.png ../_images/usb_ota_2.png
  1. 拔出 USB:自动检测并执行升级

  • 仅资源升级:显示 copy 进度 → 完成后重启至主界面

../_images/usb_ota_res_1.png ../_images/usb_ota_res_2.png ../_images/usb_ota_master.png
  • 含代码升级:copy 完成 → 重启至 OTA manager 安装 → 完成后重启至主界面

../_images/usb_ota_code_1.png ../_images/usb_ota_code_2.png ../_images/usb_ota_code_3.png ../_images/usb_ota_master.png

5. U盘的插拔检测

5.1 U盘插入检测

当前版本的 U 盘插拔检测逻辑主要集中在 usb_detection.c 中,整体链路如下:

  1. 引脚/事件检测

    • 在 HCPU 侧,usb_hcpu_service_init 通过 datac_subscribe("pin_l", irq_usb_service_callback, ...) 订阅 USB 检测引脚 INSERT_DETE_USB_PIN 的中断事件。

    • 当插入 U 盘时,该引脚被拉高,irq_usb_service_callback 收到 MSG_SERVICE_DATA_NTF_IND,进行一次 rt_thread_mdelay(200) 的防抖后,通过 rt_pin_read(INSERT_DETE_USB_PIN) 读取当前电平。

    • 若电平为高且当前状态为 USB_CONTROL_DISABLED,则调用 usb_set_irq_state(USB_CONTROL_ENABLED) 并向事件对象发送 USB_CONTROL_ENABLED

      usb_set_irq_state(USB_CONTROL_ENABLED);
      rt_event_send(usb_dete_sema, USB_CONTROL_ENABLED);
      
  2. 事件线程统一处理

    • 初始化时 app_usb_init 创建事件 usb_dete_sema,并启动线程 usb_thread_task

      usb_dete_sema = rt_event_create("usb_dete", RT_IPC_FLAG_FIFO);
      rt_thread_init(&usb_pin_thread, "usb_thread_task", usb_thread_task, ...);
      rt_thread_startup(&usb_pin_thread);
      
    • 线程中通过事件位统一处理插入、拔出和 GUI 通知:

      rt_event_recv(usb_dete_sema,
                                  USB_CONTROL_ENABLED | USB_CONTROL_DISABLED |
                                  USB_CONTROL_GUI    | USB_CONTROL_UNMOUNT_ENABLED |
                                  USB_CONTROL_UNMOUNT_DISABLED,
                                  RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
                                  RT_WAITING_FOREVER,
                                  &recved);
      
    • 当收到 USB_CONTROL_ENABLED 时,调用 app_usb_function(USB_CONTROL_ENABLED) 进入 USB 启动流程。

  3. USB 协议栈的打开(usb_start / enabled_usb)

    • app_usb_function(USB_CONTROL_ENABLED) 中:

      if (USB_CONTROL_ENABLED == usb_stat)
      {
      #ifdef BSP_PM_FREQ_SCALING
              rt_pm_hw_device_start();
      #endif
              usb_start();
      }
      
    • usb_start 负责:

      • 启动协议检测定时器:usb_timer_start();

      • 首次插入时申请电源管理:

        if (!usb_pm_flag)
        {
                rt_pm_request(PM_SLEEP_MODE_IDLE);  // 禁止深度睡眠,仅保留 IDLE
                usb_pm_flag = 1;
        }
        
      • 调用 enabled_usb() 打开 USB 协议栈和电源:

        extern void BSP_USB_Power_Up(void);
        BSP_USB_Power_Up();
        HAL_RCC_EnableModule(RCC_MOD_USBC);
        struct rt_device *usb_device = rt_device_find("usb_reg");
        if (usb_device)
                rt_device_control(usb_device, RT_DEVICE_OFLAG_OPEN, RT_NULL);
        
  4. 定时器轮询 USB 协议枚举结果

    • app_usb_init 中通过 usb_timer_create() 创建软定时器,周期为 USB_TIME_OUT(当前为 100ms):

      usb_timer_handler = rt_timer_create("usb_timer",
                                                                              usb_timerout,
                                                                              0,
                                                                              rt_tick_from_millisecond(USB_TIME_OUT),
                                                                              RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER);
      
    • 定时器回调 usb_timerout 中调用 usb_handler() 轮询 USB 协议栈状态:

      static uint8_t usb_handler(void)
      {
              static uint8_t usb_protocol_flag = 0;
              udevice_t usb_device = rt_usbd_find_device(usb_dcd);
      
              if (USB_STATE_CONFIGURED == usb_device->state ||
                      USB_STATE_ADDRESS   == usb_device->state)
              {
                      device_status_set(USB_CONNECT_STATUS, USB_IN);
                      usb_protocol_flag = 0;
                      usb_timer_stop();
                      rt_event_send(usb_dete_sema, USB_CONTROL_GUI);  // 通知 GUI U 盘已就绪
              }
              else
              {
                      usb_protocol_flag++;
                      if (usb_protocol_flag == 10)  // 约 1s 内未枚举成功
                      {
                              usb_stop();              // 关闭 USB 协议栈
                              usb_timer_stop();
                              usb_protocol_flag = 0;
                      }
              }
              return 0;
      }
      
    • 这样可以在插入后自动判定 PC 是否在超时时间内完成枚举:成功则设置 USB_IN 状态并通知 GUI,失败则自动关闭 USB,避免长时间空耗功耗。

5.2 U盘拔出检测

U 盘拔出时的处理流程与插入类似,同样通过事件线程统一调度,关键点如下:

  1. 引脚检测到拔出并上报事件

    • 当 USB 检测引脚 INSERT_DETE_USB_PIN 由高变低时,irq_usb_service_callback 在防抖后读取到 read_pin == 0,且当前状态为 USB_CONTROL_ENABLED

      else if (USB_CONTROL_ENABLED == usb_get_irq_state())
      {
              LOG_I("%s USB_CONTROL_PIN_OUT", __func__);
              usb_set_irq_state(USB_CONTROL_DISABLED);
              rt_event_send(usb_dete_sema, USB_CONTROL_DISABLED);
      }
      
  2. 事件线程触发关闭流程

    • usb_thread_task 收到 USB_CONTROL_DISABLED 时,调用 app_usb_function(USB_CONTROL_DISABLED)

      case USB_CONTROL_DISABLED:
              app_usb_function(USB_CONTROL_DISABLED);
              break;
      
  3. USB 协议栈关闭与 GUI/OTA 通知

    • app_usb_function 中:

      else if (USB_CONTROL_DISABLED == usb_stat)
      {
              usb_stop();
              usb_to_msg_gui(NULL, 0, USB_GUI_MSG_ID);  // 通知 GUI U 盘已拔出
      #ifdef BSP_PM_FREQ_SCALING
              rt_pm_hw_device_stop();                   // 允许再次降频
      #endif
      #if defined(USB_OTA)
              usb_ota_to_msg_app();                     // 有 OTA 功能时,触发升级检测
      #endif
      }
      
  4. 文件分区的卸载与重新挂载

    • usb_stop() 内部首先关闭 USB 协议栈,然后仅在 本次会话期间 PC 端确实挂载过分区(状态为 USB_IN)时执行文件系统修复与重新挂载:

      static void usb_stop(void)
      {
              disabled_usb();
              if (USB_IN == device_status_get(USB_CONNECT_STATUS))
              {
              #if defined(RT_USB_DEVICE_MSTORAGE)
              #ifdef USING_FAT_CHECK
                      int cmd_fsck(int argc, char **argv);
                      char *argv[3] = {"fsck", "-ys", usb_device_name};
                      cmd_fsck(3, argv);               // 对 U 盘对应分区做一次一致性检查
              #endif
                      usb_mount_fs();                  // 在本地重新挂载该分区
              #endif
              }
              ...
      }
      
    • 这样设计的目的:

      • PC 端拔出 U 盘后,先卸载 PC 视角下的磁盘

      • 然后在设备本地执行 fsck(可选)和 usb_mount_fs(), 确保本地文件系统处于干净状态,后续在设备侧继续访问该分区不会因为 PC 未正常弹出而损坏文件系统。

  5. USB 协议栈与电源的真正关闭(disabled_usb + PM 释放)

    • usb_stop() 中在处理完文件系统后,会释放电源管理占用并关闭 USB 协议栈:

      if (usb_pm_flag)
      {
      #ifdef RT_USING_PM
              rt_pm_release(PM_SLEEP_MODE_IDLE);   // 退出 IDLE,允许系统再次进入低功耗
      #endif
              usb_pm_flag = 0;
      }
      
      device_status_set(USB_CONNECT_STATUS, USB_OUT);
      udevice_t usb_device = rt_usbd_find_device(usb_dcd);
      usb_device->state = USB_STATE_NOTATTACHED;  // 清空 USB 协议状态
      
    • disabled_usb() 本身负责关闭底层 USB 模块:

      static void disabled_usb(void)
      {
              struct rt_device *usb_device = rt_device_find("usb_reg");
              if (usb_device)
                      rt_device_control(usb_device, RT_DEVICE_OFLAG_CLOSE, RT_NULL);
      }
      

综合来看:

  • 插入路径:检测引脚 → 事件 USB_CONTROL_ENABLEDusb_start → 打开 USB 电源与协议栈 → 定时器轮询直到枚举成功(USB_IN)并通知 GUI;

  • 拔出路径:检测引脚 → 事件 USB_CONTROL_DISABLEDusb_stop → 关闭协议栈、电源管理 → 对已挂载过的分区执行 fsck(可选)并重新挂载 → 通知 GUI、触发 OTA 检测(可选)。

56. 常见报错

6.1 检测不到 USB 协议

  • 检查 USB 时钟是否为 60MHz(在bsp_init.c或brv_io.c中确认)

在bf0_hal_pcd.c中

HAL_RCC_DisableModule(RCC_MOD_USBC);
  • 检查是否有代码关闭 USB 模块(如HAL_RCC_DisableModule(RCC_MOD_USBC);)

  • 确认 USB 引脚连接正确

6.2 PC 识别到 MSTORAGE 设备但无 U 盘

  • 检查 USB 协议通信是否正常,可使用 wireshark 抓取 USB 协议包分析

  • 确认 menuconfig 中分区配置正确(分区名或 DHARA 映射名)

  • 检查挂载的分区大小与设备的容量大小是否一致,不一致会导致挂载失败

6.3 主机cp文件失败

  • 在主机上向 U 盘拷贝文件失败,或在格式化 U 盘时出现死机 / 失败,通常是因为 PC 端看到的分区大小与设备内部实际分区大小不一致 所致。 例如:在 excel 配置表中将分区 A 设置为 1 GB,但上电后设备实际识别到该分区只有 500 MB(flash 中残留了旧的分区信息,导致当前挂载的分区大小与 excel 配置不匹配)。 此时再将该分区通过 USB 暴露给主机时,软件仍按 excel 中的 1 GB 配置上报给 PC,PC 认为该分区有 1 GB 空间,因此在写入或格式化时会按 1 GB 空间操作,最终触发 flash 读写越界错误(SDIO 方案下可能直接报错)甚至导致系统死机。

解决方法:在板端对该分区执行一次手动格式化(如执行 mkfs xxx),使实际分区大小与 excel 中的配置保持一致。