2.18. USB

Overview

提供 usb 应用示例、配置介绍和常见问题。USB各类协议文件均存放在 apps/common/usb 文件夹下。

2.18.1. 应用实例

示例演示:

  • USB做masstorge主从机方法

  • USB做UAC主从机方法

  • USB做UVC主从机方法

  • USB做HID主从机方法

  • USB与PC通信

2.18.2. 公共配置说明

  • 1.在 apps/demo/demo_DevKitBoard/app_main.c 中增加USB任务名字如下
    /*任务列表 */
    const struct task_info task_info_table[] = {
        {"app_core",            15,     2048,     1024 },
        {"sys_event",           29,     512,       0 },
        {"systimer",            14,     256,       0 },
        {"sys_timer",            9,     512,      128 },
    #if CPU_CORE_NUM > 1
        {"#C0usb_msd",           1,      512,   128   },
    #else
        {"usb_msd",              1,      512,   128   },
    #endif
    
        {0, 0, 0, 0, 0},
    };
    

  • 2.在 apps/demo/demo_DevKitBoard/board/wl82/DevKitBoard.c 中增加USB配置如下

    #if TCFG_USB_SLAVE_ENABLE || TCFG_USB_HOST_ENABLE
    #include "otg.h"
    #include "usb_host.h"
    #include "usb_common_def.h"
    #include "usb_storage.h"
    #include "asm/uvc_device.h"
    #endif
    
    
    #if TCFG_USB_SLAVE_ENABLE || TCFG_USB_HOST_ENABLE
    static const struct otg_dev_data otg_data = {
        .usb_dev_en = 0x01, //USB口使能:0x1-->USB0;0x2-->USB1;0x3--->USB0和USB1
    #if TCFG_USB_SLAVE_ENABLE
        .slave_online_cnt = 10,//USB从机的上线检测次数,单位为detect_time_interval值
        .slave_offline_cnt = 10,//USB从机的下线检测次数,单位为detect_time_interval值
    #endif
    #if TCFG_USB_HOST_ENABLE
        .host_online_cnt = 10,//US主机的上线检测次数,单位为detect_time_jjjinterval值
        .host_offline_cnt = 10,//US主机的下线检测次数,单位为detect_time_interval值
    #endif
        .detect_mode = OTG_HOST_MODE | OTG_SLAVE_MODE | OTG_CHARGE_MODE,//检测模式:主机检测、从机检测、拔插充电检测。
        .detect_time_interval = 50,//检测上下线的时间间隔,单位ms,默认50ms(不建议修改变小,否则USB事件可能会丢,最低为10ms)。
    };
    #endif
    
    
    REGISTER_DEVICES(device_table) = {
    #if TCFG_USB_SLAVE_ENABLE || TCFG_USB_HOST_ENABLE
        { "otg", &usb_dev_ops, (void *)&otg_data},
    #endif
    
    };
    

    USB的拔插事件通知在 apps/common/system/device_mount.cdevice_event_handler 函数,当注册app状态机,则状态机事件处理也会有事件通知(详情 app_state_machine )。

2.18.3. 操作说明

  • 1.USB做masstorge从机方法 打开四个宏即可

    • 1.1 配置SD卡相关参考 SD例子

    • 1.2 app_config.h 修改配置, 注意:读卡器功能,同时需要打开SD卡功能并插SD卡,电脑端才能显示读卡器的U盘盘符。

      #define TCFG_SD0_ENABLE  1   //使能sd卡
      #define TCFG_PC_ENABLE  1     //使用从机模式
      #define USB_PC_NO_APP_MODE                  2 //不使用app_core
      #define USB_DEVICE_CLASS_CONFIG    (MASSSTORAGE_CLASS)   //msd
      

  • 2.USB做UAC从机方法

    • 2.1 配置AUDIO工程相关参考 demo_audio工程

    • 2.2 app_config.h 修改配置

      #define TCFG_PC_ENABLE  1     //使用从机模式
      #define USB_DEVICE_CLASS_CONFIG    (AUDIO_CLASS)   //audio
      #define CONFIG_PCM_ENC_ENABLE
      #define CONFIG_PCM_DEC_ENABLE
      
      #define CONFIG_AUDIO_ENC_SAMPLE_SOURCE      AUDIO_ENC_SAMPLE_SOURCE_MIC
      

    • 2.3 增加uac相关库

      audio_config.c | 用到了相关的api函数(get_ps_cal_api)

      usb_audio_api.c | usb和audio对接api函数

      audio_server.a | 音频编解码库

      lib_usb_syn.a | 用于变采样 专用于usb 不能用于其他地方

    • 2.4 在 board.c 初始化dac相关硬件

      void board_early_init()
         {
             extern void dac_early_init(u8 trim_en, u8 pwr_sel, u32 dly_msecs);
             dac_early_init(1, 1, 1000);
             devices_init();
         }
         \
      

    • 2.5 在 app_main.c 增加相关代码

      {"uac_sync",            20,      512,   0     },
      {"uac_play",             7,      768,   0     },
      {"uac_record",           7,      768,   32    },
      

  • 3.USB做UVC从机方法 (支持DVP摄像头数据到电脑)

    • 3.1 配置VIDEO工程相关参考 demo_video工程 ,先要移植摄像头并调试可以出图,电脑端才能有图像。

      //*********************************************************************************//
      //          USB配置    USB查看软件 请在sdk_tools文件夹中找到AMCapSetup.exe            //
      //*********************************************************************************//
      
      #define TCFG_PC_ENABLE                                              1    //打开usb从机功能(打开连接PC电脑模式)
      #if TCFG_PC_ENABLE
      #define USB_PC_NO_APP_MODE                                  2   //不用APP状态机接收消息
      #define USB_MALLOC_ENABLE                     1   //buff采用申请方式
      #define USB_DEVICE_CLASS_CONFIG (UVC_CLASS)     //从机UVC电脑查看功能选择
      //#define USB_DEVICE_CLASS_CONFIG (MASSSTORAGE_CLASS) //从机电脑盘符功能选择
      #if ((USB_DEVICE_CLASS_CONFIG & UVC_CLASS) == UVC_CLASS)
      #define CONFIG_USR_VIDEO_ENABLE
      #endif
      #endif
      

    • 3.2 增加uvc相关库

      stream_protocol.c | 实时流中间层

      user_video_rec.c | 打开实时流api

      video_rt_usr.c | 封装给stream_protocol

      gc0308.c | 摄像头驱动,根据实际摄像头选择驱动

  • 3.3 uvc从机流程

    (1)、电脑开启UVC摄像头:设备USB连接电脑,电脑开启摄像头时,系统会进 uvc.cuvc_vs_itf_handle 函数,执行打开摄像头 usb_video_open_camera()

    static u32 uvc_vs_itf_handle(struct usb_device_t *usb_device, struct usb_ctrlrequest *req)
    {
      ...
      case USB_TYPE_STANDARD:
      ...
        usb_video_open_camera(usb_id);
      ...
    }
    ///执行打开摄像头,指向函数uvc_video_open()
    static void usb_video_open_camera(usb_dev usb_id)
    {
    ...
    uvc_handle[usb_id]->video_open(usb_id, uvc_slave_probe_commit_control[usb_id][2] - 1,  //Format Index
                                  uvc_slave_probe_commit_control[usb_id][3] - 1,  //Frame  Index
                                  10000000 / fps);
    }
    
    static int uvc_video_open(int idx, int fmt, int frame_id, int fps)
    {
    ...
    #if UVC_FORMAT_MJPG
    set_video_rt_cb(uvc_send_buf, &uvc_video_info);//注册JPEG回调函数
    //uvc_video_open函数调用user_video_rec.c里的API,实现打开摄像头出JPG图
    user_video_rec0_open(0);
    }
    

    (2)、UVC发送JPEG到电脑:电脑请求设备UVC的数据包,则进入 uvc_g_bulk_transfer() 函数,进行JPEG图片获取,最终跑到 uvc_video_req_bug() 函数。

    static void uvc_g_bulk_transfer(struct usb_device_t *usb_device, u32 ep)
    {
    ...
    //该函数为uvc_video_req_buf()
    len = uvc_handle[usb_id]->video_reqbuf(usb_id, tx_addr, UVC_FIFO_TXMAXP, &IsEOFFrame);
    ...
    //发送JPEG到USB
    usb_g_bulk_write(usb_id, UVC_STREAM_EP_IN, tx_addr, len);
    }
    
    static int uvc_video_req_buf(int idx, void *buf, u32 len, u32 *frame_end)
    {
    ...
    //复制JPEG到USB缓存,下一步发送到USB
    memcpy(buf, uvc_video_info.reqbuf, uvc_video_info.send_len);
    ...
    }
    

    1. USB做HID从机方法(演示做键盘的方法)

    • 4.1 在 apps/demo/demo_DevKitBoard/include/demo_config.h ,开启宏 USE_USB_HID_SLAVE_TEST_DEMO

    • 4.2 增加 hid_keyboard.c

    • 4.3 在 app_config.h 修改宏和选择增加枚举键盘还是pos机,pos机流程一样,具体可以参考 apps/scan_box/app_main.c

      #define USB_DEVICE_CLASS_CONFIG    (HID_CLASS)
      
      #if TCFG_USB_SLAVE_HID_ENABLE
      #define USB_HID_KEYBOARD_ENABLE             1
      

    1. USB做主机读U盘

    • 5.1 在 board.c 增加配置

      { "udisk", &mass_storage_ops, NULL },
      
    • 5.2 在 app_config.h 打开宏

       #define TCFG_UDISK_ENABLE                   1    //U盘功能
      // #define USB_DEVICE_CLASS_CONFIG                (AUDIO_CLASS)
      

    • 5.3 在 apps/demo/demo_DevKitBoard/include/demo_config.h ,开启宏 USE_USB_UDISK_HOST_TEST_DEMO

    1. USB做UVC主机(设备USB口接上UVC摄像头)

    • 6.1 在 app_config.h 打开宏

      #define TCFG_HOST_UVC_ENABLE                 1   //打开USB 后拉摄像头功能,需要使能住机模式
      

    • 6.2 查看USB摄像头方法:

      A:设备连接后,打开电脑自带的相机出图。下面三种方法是跑 wifi_camera 工程时的方法。

      B:使用DVrunning2的APP可以直接连接wifi热点出图。

      C:使用 wifi_camerademo_ui ,则开启UI,打开UI宏:codeblock的工程编译选项(build options)的#define选项加上CONFIG_UI_ENABLE,可以显示到屏幕(或者linux环境下的makefile加上UI宏CONFIG_UI_ENABLE)。

      D: 在 apps\wifi_camera\app_main.c 开启插卡录像功能,如下代码块。插USB UVC摄像头后,再插SD卡就可以开启录像功能,在录像一个文件结束(默认3分钟)就可以拔出SD卡来插卡查看UVC录像视频。

  • 6.3 UVC主机流程:

    (1)、打开UVC。以user_video_rec2.c为例子,user_video_rec2.c中的user_video_rec0_open()调用server_open()。

    (2)、中断接收数据。

    //*************usb_video.c****************//
    int uvc_host_open_camera(void *fd)
    {
    ...
    usb_uvc_info(device);//发送配置到从机UVC
    ret = usb_control_msg(host_dev, USB_REQ_SET_INTERFACE, 0x01, uvc->if_alt_num, hdl->itf_base + 1, NULL, 0);
    log_debug("USB_REQ_SET_INTERFACE %d to %d", hdl->itf_base + 1, uvc->if_alt_num);
    check_ret(ret);
    iso_ep_rx_init(device);//中断接收初始化
    uvc->camera_open = 1;
    ...
    }
    
    static void vs_iso_handler(struct usb_host_device *host_dev, u32 ep)
    {
    //中断函数,数据包读取,数据包回调
    ...
    //请求下一个数据包
    usb_h_ep_read_async(usb_id, uvc->host_ep, uvc->ep, NULL, 0, USB_ENDPOINT_XFER_ISOC, 1);
    //当前数据包
    start_addr = hdl->buffer;
    ...
    //一帧JPEG结束,进入帧结束回调。uvc->stream_out实际指向uvc_host.c的uvc_mjpg_stream_out();
    if (uvc->stream_out) {
            uvc->stream_out(uvc->priv, -1, NULL, STREAM_ERR);
            stream_state = STREAM_ERR;
        }
    }
    //*************uvc_host.c****************//
    int uvc_mjpg_stream_out(void *fd, int cnt, void *stream_list, int state)
    {
    ...
    uvc_buf_stream_finish(fh, fh->buf, ch);//推流往应用层
    ...
    }
    

  • 6.4 常见问题:

(1)、UVC应用层接收帧率低问题处理。

A:确定中断是否接受到足够帧,在中断函数一帧 JPEG 图片完成回调函数加打印计数等方式检测。

B:检测是否在数据包回调函数丢帧, uvc_mjpg_stream_out() 函数。打开丢帧处理打印 putchar('d');

C:在 app_config.h 注释 CONFIG_VIDEO_REC_PPBUF_MODE ,则使用 lbuf 模式。

D:加大应用层视频对应的 buf 缓冲区大小,如 user_video_rec2.c 的请求视频流的 req.rec.bufreq.rec.buf_len 参数的值。

E:APP 图传的部分详情 WIFI_CAMERA工程说明

    1. USB做HID主机方法(演示做键盘的方法)

    • 7.1 在 app_config.h 修改宏 ,开启宏 #define TCFG_HID_HOST_ENABLE

      #ifdef CONFIG_USB_ENABLE
      #define TCFG_PC_ENABLE                          0   //使用从机模式
      #define USB_PC_NO_APP_MODE                      2   //不使用app_core
      #define USB_MALLOC_ENABLE                       1
      #define USB_DEVICE_CLASS_CONFIG                 (HID_CLASS)
      #define TCFG_HOST_AUDIO_ENABLE                  0
      #define TCFG_HOST_UVC_ENABLE                    0   //打开USB 后拉摄像头功能,需要使能住机模式
      #define TCFG_HID_HOST_ENABLE                                1     //HID主机功能
      #define TCFG_UDISK_ENABLE                       0   //U盘功能
      

    1. USB做UAC主机方法,具体代码参考 uac_host_demo.c ,跑 wifi_story_machine 工程

    • 8.1 在 board_config.h 修改宏

      #ifdef CONFIG_USB_ENABLE
      #define TCFG_PC_ENABLE                      0   //使用从机模式
      #define TCFG_HOST_AUDIO_ENABLE              1     //uac主机功能,用户需要自己补充uac_host_demo.c里面的两个函数
      

2.18.4. USB摄像头使用说明(UVC)

    1. 例如:使用工程 wifi_camera 进行编译测试 。

    1. 配置芯片使用SROM 即 去掉 CONFIG_NO_SDRAM_ENABLE宏 一般在全局宏配置。

    1. app_config.h 中将CONFIG_UVC_VIDEO2_ENABLE宏开启。

    1. video_buf_config.h 中改大NET_VREC0_FBUF_SIZE宏大小。

    1. 将CONFIG_VIDEO_REC_PPBUF_MODE宏进行屏蔽 屏蔽默认使用LBUF 即帧buf。

    1. 有关帧数使用规格640*480 25fps(USB摄像头) wifi图传20fps。

    1. 当想要使用UI显示UVC摄像头时候需要提前确认好屏幕驱动已经调好能正常显示,然后将CONFIG_UI_ENABLE宏开启 并且在 app_config.h 中将TCFG_DEMO_UI_RUN, NO_UI_LCD_TEST 均配置为 1。(其他工程暂不支持)

    1. 支持屏幕和UVC同时显示摄像头内容。

2.18.5. USB与PC通信

列举了两种USB与PC端通信协议的使用方式,SCSI协议与CDC协议,可参考应用于PC端的上位机开发。

  • 1.USB SCSI协议的使用

    • 1.USB配置。SCSI协议一般用于U盘、读卡器等大容量存储设备。在SDK中,将USB配置成从机模式,并配置设备类型为存储设备;在 app_config.h 文件中进行配置,如下:

      #define TCFG_PC_ENABLE          1    //打开usb从机功能(打开连接PC电脑模式)
      
      #define USB_DEVICE_CLASS_CONFIG (MASSSTORAGE_CLASS) //从机电脑盘符功能选择
      

    • 2.SCSI协议数据处理。在 include_lib/driver/device/usb/scsi.h 文件中,添加自定义的SCSI协议的操作码,注意不能和现有的操作码重复,如下:

      #define MY_TEST_OPCODE        0x7d
      

    • 3.在 apps/common/usb/device/msd.c 文件中,添加SCSI协议数据的处理函数,如下:

      enum {
        TEST_CMD_0,
        TEST_CMD_1,
        };
      void test_cmd_deal(const struct usb_device_t *usb_device)
      {
        u8 len;
        u8 buf[32];
      
        switch (msd_handle[usb_id]->cbw.lun) {
            case TEST_CMD_0:
                printf("^^^TEST_CMD_0\n");
                //返回数据至USB主机
                memset(buf, 0xaa, sizeof(buf));
                msd_mcu2usb(usb_device, buf, sizeof(buf));
                break;
            case TEST_CMD_1:
                printf("^^^TEST_CMD_1\n");
                //接收USB主机发送的数据
                len = msd_handle[usb_id]->cbw.LengthL;
                msd_usb2mcu(usb_device, buf, len);
                put_buf(buf, sizeof(buf));
                break;
            default:
                printf("^^^no support cmd\n");
                break;
        }
      }
      
      void USB_MassStorage(const struct usb_device_t *usb_device)
      {
        //......
        switch (msd_handle[usb_id]->cbw.operationCode) {
            //......
            case MY_TEST_OPCODE:  //自定义的操作码处理
                test_cmd_deal(usb_device);
                break;
            //......
        }
      }
      

    • 4.数据收发测试。可使用USB调试助手,向USB从机发送SCSI命令进行测试,如下:

  • 2.USB CDC协议的使用

    • 1.USB配置。CDC协议将USB设备枚举为虚拟串口,PC端通过串口协议与USB从机进行通信。在SDK中,将USB配置成从机模式,并配置设备类型为CDC类设备;在 app_config.h 文件中进行配置,如下:

        //3.从机连接PC模式下:电脑盘符功能、UVC功能
      #define TCFG_PC_ENABLE                1    //打开usb从机功能(打开连接PC电脑模式)
      #if TCFG_PC_ENABLE
      ...
      #define USB_DEVICE_CLASS_CONFIG (CDC_CLASS) //从机电脑盘符功能选择
      #endif
      

    • 2.CDC传输中断使能。在 apps/common/usb/device/cdc.c 中,开启cdc协议接收中断:

      static void cdc_endpoint_init(struct usb_device_t *usb_device, u32 itf)
      {
      ......
        usb_g_set_intr_hander(usb_id, CDC_DATA_EP_OUT | USB_DIR_OUT, cdc_intrrx); //打开使能cdc中断
        /* usb_g_set_intr_hander(usb_id, CDC_DATA_EP_OUT | USB_DIR_OUT, cdc_wakeup_handler);//使能cdc中断同步注释*/
      ......
      }
      
      void cdc_register(const usb_dev usb_id)
      {
      ......
        cdc_set_output_handle(0,NULL, usb_cdc_output_handler);//注册数据处理函数
      ......
      }
      
    • 3.CDC协议数据处理。在 apps/common/usb/device/cdc.c 中,添加自定义的CDC协议数据处理代码。(该代码默认关闭,如需开启,把 late_initcall(cdc_test_main) 的注释去除即可)如下:

      static OS_SEM psem;
      static u8 rx_buf[64];
      static u8 rx_len;
      s32 usb_cdc_output_handler(void *priv, u8 *buf, u32 len)
      {
      //将收到的数据打印
          printf("len = %d, buf = %s\n", len, buf);
        rx_len = len;
        memcpy(rx_buf, buf, len);
      //post信号量
        os_sem_post(&psem);
          return 0;
      }
      void cdc_rx_test()
      {
        os_sem_create(&psem, 0);
        while(1){
      //pend信号量
          os_sem_pend(&psem, 0);
          printf("len = %d, buf = %s\n", rx_len, rx_buf);
      //将收到的数据发送至终端
          cdc_write_data(0, &rx_buf, rx_len);
          printf("cdc_test\n");
        }
      }
      void cdc_test_main()
      {
          if (thread_fork("cdc_rx_test", 10, 512, 0, NULL, cdc_rx_test, NULL) != OS_NO_ERR) {
              printf("thread fork fail\n");
          }
          return;
      }
      late_initcall(cdc_test_main);
      
    • 4.数据收发测试。可通过串口调试助手进行收发测试,如下:

    • 5.若想使用USB虚拟串口打印log:

      apps/common/debug/debug_user.c 打开宏 #define CONFIG_USB_DEBUG_ENABLE     //开关USB虚拟串口打印信息

2.18.6. 常见问题

1. 宏TCFG_PC_ENABLE,是USB从机功能总包起来的宏,具体的定义可以进入 apps/common/usb/usb_std_class_def.happs/common/usb/usb_common_def.h 中查看。 如需移植usb从机功能时,需包含 usb_std_class_def.husb_common_def.h 这两个文件。 2. 需要移植usb功能时,需要添加 apps/common/system/device_mount.c 文件。