打印
[其他]

基于MM32F0163D7P的USB接口TinyUSB应用:移植和新增设备(一)

[复制链接]
557|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 MindMotion 于 2023-8-31 10:59 编辑

1. TinyUSB基本介绍

TinyUSB是一个用于嵌入式系统的开源的跨平台USB协议栈,协议栈中包含了主机端及设备端的协议栈,由于不使用动态内存分配以及采用阻塞所有中断事件,在非ISR任务功能中处理中断事件的设计方式,所以此协议栈的内存安全性及线程安全性极高。

源码是托管在GitHub上面,地址是:https://github.com/hathach/tinyusb



2. TinyUSB基本移植介绍

MM32已基于TinyUSB开发完成相应的参考例程,并可以给客户提供参考,本次我们介绍的移植将基于此基础上进行。将TinyUSB从GitHub上克隆到本地,可以得到如下内容:


图1 TinyUSB源码

移植TinyUSB到MM32F0160需要添加如下文件:

属于工程专用文件:


[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]usb_descriptors.c
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]usb_dcd_port.c
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]tusb_config.h
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]usb_descriptors.h


属于库内文件:

[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]tusb.c
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]usbd.c
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]usbd_control.c
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]*_device.c
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]tusb_fifo.c


将src 整个文件夹copy替换到例程components目录下的src。
将tinyusb 目录下example下的对应文件,以device uac2_headset为例,将tinyusb\examples\device\uac2_headset\src 四个文件copy到例程user文件夹里面。



图2 源码的device文件


图3 用户工程文件

USB时钟频率是48MHz,HSE可以经过PLL倍频到48MHz或96MHz,然后经过分频到48MHz,注意需要使用外部晶振,如果使用内部时钟HSI,需要使能时钟回馈系统CRS功能。
在main.c 增加USB时钟配置函数void USB_DeviceClockInit(void),同时将board_init();替换成usb时钟初始化函数,主频配置96MHz,USB选择PLL1输入二分频到USB。
有使用TU_LOG做串口输出,可以使能CFG_TUSB_DEBUG为需要的输出等级,同时将


[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]#define tu_printf    printf

改到串口输出,Keil Options->Target 勾选Use MicroLIB,并实现重定向函数。

//------------- clock initial -------------//
void USB_DeviceClockInit(void)         //HSE 96M
{
    /* Select USBCLK source */
    RCC->CFGR &= ~(1 << 19);           //USB CLK SEL PLL1

    RCC->CFGR &= ~(0x03 << 22);
    RCC->CFGR |= 0x01 << 22;

    RCC_AHBPeriphClockCmd(RCC_AHBENR_USB, ENABLE);
}

//------------- MAIN -------------//
int main(void)
{
    USB_DeviceClockInit();             //board_init();

    CONSOLE_Init(460800);              //enable printf debug

    //init device stack on configured roothub port
    tud_init(BOARD_TUD_RHPORT);

    TU_LOG1("Headset running\r\n"); //CFG_TUSB_DEBUG for debugging #if CFG_TUSB_DEBUG

    //0 : no debug
    //1 : print error
    //2 : print warning
    //3 : print info
    while (1)
    {
        tud_task();                    //tinyusb device task
        led_blinking_task();
        audio_task();
    }
}


添加tud_dcd_port.c 接口函数文件,Keil下Options C/C++勾选C99和GNU externsions(tud_dcd_port.c 文件可以参考现有例程或者联系灵动技术支持)。


图4 工程设置

移植修改其他设备基本流程和上述一致,将tinyusb 目录example\device\ 里面将想要修改的设备src文件夹里面四个文件copy到例程user文件夹里面替换。

3. 修改一个uac2_headset Device设备

在克隆下来的的文件夹examples\device里面找到需要修改的device设备,本次修改uac2_headset。将里面的文件都copy到工程USER目录里面,然后Keil工程按如下文件树添加对应文件。



图5 uac2_headset设备描述符文件

文件树:

[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]1.TinyUSB_UAC
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]2.    │
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]3.    ├─USER
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]4.    │       main.c
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]5.    │       usb_descriptors.c
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]6.    │       usb_dcd_port.c
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]7.    │  
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]8.    └─TinyUSB
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]9.            tusb.c
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]10.            audio_device.c
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]11.            tud_fifo.c
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]12.            usbd.c
[color=rgba(0, 0, 0, 0.9)][backcolor=rgba(29, 131, 255, 0.08)]13.            usb_control.c


在tusb_config.h文件里面CLASS将对应的设备define改成1 ( #define CFG_TUD_AUDIO  1 ) ,使能AUDIO设备。

//------------- CLASS -------------//
#define CFG_TUD_CDC               0
#define CFG_TUD_MSC               0
#define CFG_TUD_HID               0
#define CFG_TUD_MIDI              0
#define CFG_TUD_AUDIO             1
#define CFG_TUD_VENDOR            0


按照前面的移植步骤,只需要修改main.c 里面的时钟初始化部分即可,其他的device设备修改流程一致。

4. 新增一个设备变成复合设备

USB设备主要四个描述符,分别是设备描述符(Device Descriptors),配置描述符(Configuration Descriptor),报告描述符(Configuration Descriptor)和字符描述符(String Descriptors)。

添加复合设备device文件,本例程在上述3(修改一个uac2_headset Device设备)例程的基础上增加一个HID设备变成复合设备,首先将工程目录下的components\tinyusb\src\class\hid\hid_device.c文件添加到工程。



图6 源码HID设备参考文件


图7 添加device文件

在tusb_config.h 文件里面 CLASS 使能HID宏,本例程是复合设备(Audio+HID)所以两个宏都为1。

//------------- CLASS -------------//
#define CFG_TUD_CDC               0
#define CFG_TUD_MSC               0
#define CFG_TUD_HID               1
#define CFG_TUD_MIDI              0
#define CFG_TUD_AUDIO             1
#define CFG_TUD_VENDOR            0

在usb_descriptors.c 文件里面添加HID的描述符,增加HID Report Descriptor相关函数。

//--------------------------------------------------------------------+
//HID Report Descriptor
//--------------------------------------------------------------------+
uint8_t const desc_hid_report[] =
{
    TUD_HID_REPORT_DESC_GENERIC_INOUT(CFG_TUD_HID_EP_BUFSIZE)
};

//Invoked when received GET HID REPORT DESCRIPTOR
//Application return pointer to descriptor
//Descriptor contents must exist long enough for transfer to complete
uint8_t const *tud_hid_descriptor_report_cb(uint8_t itf)
{
    (void)itf;
    return (desc_hid_report);
}


在usb_descriptors.c 文件里面添加HID Descriptor length(注意:长度一定要和下面DESCRIPTOR对应,否则枚举会失败)。

#define CONFIG_TOTAL_LEN        (TUD_CONFIG_DESC_LEN + CFG_TUD_AUDIO * TUD_AUDIO_HEADSET_STEREO_DESC_LEN + TUD_HID_INOUT_DESC_LEN)


在usb_descriptors.c 文件里面添加HID描述符文件,例程使用的是TUD_HID_INOUT_DESCRIPTOR,和上述CONFIG_TOTAL_LEN里面的TUD_HID_INOUT_DESC_LEN对应,然后配置HID IN OUT通讯选择哪个端点。

#define EPNUM_HID               0x03

uint8_t const desc_configuration[] =
{
    //Interface count, string index, total length, attribute, power in mA
    TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),

    //Interface number, string index, EP Out & EP In address, EP size
    TUD_AUDIO_HEADSET_STEREO_DESCRIPTOR(2, EPNUM_AUDIO_OUT, EPNUM_AUDIO_IN | 0x80),

    //Interface number, string index, protocol, report descriptor len, EP Out & In address, size & polling interval
    TUD_HID_INOUT_DESCRIPTOR(ITF_NUM_HID, 6, HID_ITF_PROTOCOL_NONE,  sizeof(desc_hid_report), EPNUM_HID, 0x80 | EPNUM_HID,CFG_TUD_HID_EP_BUFSIZE, 10),
};

在usb_descriptors.c 文件里面添加 HID string字符串。

//array of pointer to string descriptors
char const *string_desc_arr[] =
{
    (const char[]){ 0x09, 0x04 },    //0: is supported language is English (0x0409)
    "TinyUSB",                         //1: Manufacturer
    "TinyUSB headset",                //2: Product
    "000001",                          //3: Serials, should use chip ID
    "TinyUSB Speakers",               //4: Audio Interface
    "TinyUSB Microphone",             //5: Audio Interface
    "TinyUSB HID",                     //6: HID Interface
};

在usb_descriptors.h 文件里面ITF_NUM_TOTAL 增加一个 ITF_NUM_HID。

enum
{
    ITF_NUM_AUDIO_CONTROL = 0,
    ITF_NUM_AUDIO_STREAMING_SPK,
    ITF_NUM_AUDIO_STREAMING_MIC,
    ITF_NUM_HID,
    ITF_NUM_TOTAL
};

在main.c 里面增加hid_task(); 然后将HID的其他处理函数添加到main.c。

/*------------- MAIN -------------*/
int main(void)
{
  USB_DeviceClockInit();//board_init();

  CONSOLE_Init(460800);  //enable printf debug

  // init device stack on configured roothub port
  tud_init(BOARD_TUD_RHPORT);

  TU_LOG1("UAC2 Headset & HID running\r\n");   /// CFG_TUSB_DEBUG for debugging #if CFG_TUSB_DEBUG
  // 0 : no debug
  // 1 : print error
  // 2 : print warning
  // 3 : print info

  while (1)
  {
    tud_task(); // TinyUSB device task
    audio_task();
    hid_task();
  }

  return 0;
}

在hid_task()函数中添加需要处理的用户程序。

//--------------------------------------------------------------------+
// USB HID
//--------------------------------------------------------------------+
uint8_t hid_report_data[64];

static void send_hid_report(uint8_t report_id, uint32_t btn)
{
  // skip if hid is not ready yet
  if ( !tud_hid_ready() ) return;

  switch(report_id)
  {
    case REPORT_ID_MOUSE:
    {
      int8_t const delta = 5;

      // no button, right + down, no scroll, no pan
      if(btn)
      {
        tud_hid_mouse_report(REPORT_ID_MOUSE, 0x00, delta, delta, 0, 0);
      }
    }
    break;

    default: break;
  }

}

// Every 10ms, we will sent 1 report for each HID profile (keyboard, mouse etc ..)
// tud_hid_report_complete_cb() is used to send the next report after previous one is complete
void hid_task(void)
{
  uint32_t const btn = 1u;

  // Remote wakeup
  if ( tud_suspended() && btn )
  {
    // Wake up host if we are in suspend mode
    // and REMOTE_WAKEUP feature is enabled by host
    tud_remote_wakeup();
  }else
  {
    // Send the 1st of report chain, the rest will be sent by tud_hid_report_complete_cb()
    send_hid_report(REPORT_ID_MOUSE, btn);
  }
}

5. 功能验证测试

完成上述移植,解决基本的编译问题后烧录测试能枚举正常。

图8 枚举过程

图9 枚举成功

使用特权

评论回复
沙发
zwsam| | 2023-8-31 19:30 | 只看该作者
不错啊

使用特权

评论回复
板凳
langgq| | 2023-8-31 19:53 | 只看该作者
协议栈的内存安全性及线程安全性极高

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:上海灵动微电子股份有限公司
简介:上海灵动微电子股份有限公司成立于 2011 年,是中国本土通用 32 位 MCU 产品及解决方案供应商。 灵动股份的 MCU 产品以 MM32 为标识,基于 Arm Cortex-M 系列内核,自主研发软硬件和生态系统。目前已量产近 300 多款型号,累计交付超 4 亿颗,在本土通用 32 位 MCU 公司中位居前列。

76

主题

98

帖子

4

粉丝