1、TinyUSB基本介绍
TinyUSB是一个用于嵌入式系统的开源的跨平台USB协议栈,协议栈中包含了主机端及设备端的协议栈,由于不使用动态内存分配以及采用阻塞所有中断事件,在非ISR任务功能中处理中断事件的设计方式,所以此协议栈的内存安全性及线程安全性极高。
源码是托管在GitHub上面,地址是:https://github.com/hathach/tinyusb。
2、TinyUSB基本移植介绍
MM32已基于TinyUSB开发完成相应的参考例程,并可以给客户提供参考,本次我们介绍的移植将基于此基础上进行。将TinyUSB从GitHub上克隆到本地,可以得到如下内容:
图1 TinyUSB源码
移植TinyUSB到MM32F0160需要添加如下文件:
属于工程专用文件:
usb_descriptors.c
usb_dcd_port.c
tusb_config.h
usb_descriptors.h
属于库内文件:
tusb.c
usbd.c
usbd_control.c
*_device.c
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为需要的输出等级,同时将
#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设备描述符文件
文件树:
1.TinyUSB_UAC
2. │
3. ├─USER
4. │ main.c
5. │ usb_descriptors.c
6. │ usb_dcd_port.c
7. │
8. └─TinyUSB
9. tusb.c
10. audio_device.c
11. tud_fifo.c
12. usbd.c
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 枚举成功
来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。