13.1實驗內容
通過本實驗主要學習以下內容:
13.2實驗原理
13.2.1USB通信基礎知識
USB的全稱是Universal Serial Bus,通用串行總線。它的出現(xiàn)主要是為了簡化個人計算機與外圍設備的連接,增加易用性。USB支持熱插拔,并且是即插即用的,另外,它還具有很強的可擴展性,傳輸速度也很快,這些特性使支持USB接口的電子設備更易用、更大眾化。GD32F4xx系列MCU集成了USB2.0全速OTG模塊以及高速OTG模塊。首先為大家介紹USB通信的一些基礎知識,包括USB協(xié)議、枚舉流程等,建議讀者可以多多閱讀USB協(xié)議,以更深入了解USB,USB官網鏈接如下,可參考:https://www.usb.org/
13.2.1.1USB金字塔型拓撲結構
塔頂為USB主控制器和根集線器(Root Hub),下面接USB集線器(Hub),集線器將一個USB口擴展為多個USB口,USB2.0規(guī)定集線器的層數最多為6層,理論上一個USB主控制器最多可接127個設備,因為協(xié)議規(guī)定USB設備具有一個7 bit的地址(取值范圍為0~127,而地址0是保留給未初始化的設備使用的)。
13.2.1.2NRZI編碼
USB采用差分信號傳輸,使用的是如上圖所示的NRZI編碼方式:數據為0時,電平翻轉;數據為1時,電平不翻轉。如果出現(xiàn)6個連續(xù)的數據1,則插入一個數據0,強制電平翻轉,以便時鐘同步。上面的一條線表示的是原始數據序列,下面的一條線表示的是經過NRZI編碼后的數據序列。
13.2.1.3USB數據協(xié)議
USB數據是由二進制數據串組成,首先由數據串構成包(packet),包再構成事務(transaction),事務最終構成傳輸(transfer)。
USB傳輸的最小單位為包,一個包被分成不同的域,根據不同類型的包,所包含的域是不一樣的,但是不同的包有個共同的特點,就是以包起始(SOP)開始,之后是同步域(0x00000001),然后是包內容,最后以包結束符(EOP)結束這個包。PID為標識域,由四位標識符加4位標識符反碼構成,表明包的類型和格式。根據PID的不同,USB協(xié)議中規(guī)定的包類型有令牌包、數據包、握手包和特殊包等。
USB事務通常有兩個或三個包組成:令牌包、數據包和握手包,令牌包用來啟動一個事務,總是由主機發(fā)送;數據包用來傳輸數據;握手包由數據接收者進行發(fā)送,表明數據的接收情況。批量、同步和中斷傳輸每次傳輸都是一個事務,控制傳輸包括三個階段:建立過程、數據過程和狀態(tài)過程。
針對不同的數據傳輸場景,USB分為四種數據傳輸模式,這四種傳輸模式分別由不同的包(packet)組成,并且有不同的數據處理策略。每種數據傳輸模式的流程示意圖以及應用場景如下:
- 控制傳輸一般用于命令和狀態(tài)的傳輸,分為控制讀、控制寫和無數據控制傳輸。在設備枚舉的過程中,采用控制傳輸方式進行數據傳輸。
- 批量傳輸分為批量讀和批量寫,用于數據量大、對實時性要求不高的場合,如U盤。
- 中斷傳輸用于數據量小的場合,保證查詢頻率,如鼠標、鍵盤。
- 同步傳輸用于數據量大、同時對實時性要求較高的場合,如音視頻。不保證數據完整性,沒有ACK/NAK應答包,不進行數據重傳。
13.2.1.4USB描述符
- 一個USB設備通常有一個或多個配置,但在同一時刻只能有一個配置;
- 一個配置通常有一個或多個接口;
- 一個接口通常有一個或多個端點;
在USB通信中,USB設備需要配置多個USB描述符用以枚舉階段將描述符返回給主機,用以主機的枚舉以及識別。USB描述符包括設備描述符、配置描述符、接口描述符、端點描述符以及字符串描述符等。在GD32 USBD固件庫中,針對各種描述符都按照USB協(xié)議定義了相關結構體,具體說明如下。
- 設備描述符
每個設備必須有一個設備描述符,設備描述符提供了關于設備的配置、設備所歸屬的類、設備所遵循的協(xié)議代碼、VID、PID等信息,其相關結構體定義如下。
C typedef struct _usb_desc_dev { usb_desc_header header; /*!< descriptor header, including type and size */ uint16_t bcdUSB; /*!< BCD of the supported USB specification */ uint8_t bDeviceClass; /*!< USB device class */ uint8_t bDeviceSubClass; /*!< USB device subclass */ uint8_t bDeviceProtocol; /*!< USB device protocol */ uint8_t bMaxPacketSize0; /*!< size of the control (address 0) endpoint's bank in bytes */ uint16_t idVendor; /*!< vendor ID for the USB product */ uint16_t idProduct; /*!< unique product ID for the USB product */ uint16_t bcdDevice; /*!< product release (version) number */ uint8_t iManufacturer; /*!< string index for the manufacturer's name */ uint8_t iProduct; /*!< string index for the product name/details */ uint8_t iSerialNumber; /*!< string index for the product's globally unique hexadecimal serial number */ uint8_t bNumberConfigurations; /*!< total number of configurations supported by the device */ } usb_desc_dev; |
- 配置描述符
每個USB設備都至少具有一個配置描述符,在設備描述符中規(guī)定了該設備有多少種配置,每種配置都有一個描述符,其相關結構體定義如下。
C typedef struct _usb_desc_config { usb_desc_header header; /*!< descriptor header, including type and size */ uint16_t wTotalLength; /*!< size of the configuration descriptor header, and all sub descriptors inside the configuration */ uint8_t bNumInterfaces; /*!< total number of interfaces in the configuration */ uint8_t bConfigurationValue; /*!< configuration index of the current configuration */ uint8_t iConfiguration; /*!< index of a string descriptor describing the configuration */ uint8_t bmAttributes; /*!< configuration attributes */ uint8_t bMaxPower; /*!< maximum power consumption of the device while in the current configuration */ } usb_desc_config; |
- 接口描述符
接口描述符用以描述接口信息,接口描述符不能單獨返回,必須附著在配置描述符后一并返回,其相關結構體定義如下。
C typedef struct _usb_desc_itf { usb_desc_header header; /*!< descriptor header, including type and size */ uint8_t bInterfaceNumber; /*!< index of the interface in the current configuration */ uint8_t bAlternateSetting; /*!< alternate setting for the interface number */ uint8_t bNumEndpoints; /*!< total number of endpoints in the interface */ uint8_t bInterfaceClass; /*!< interface class ID */ uint8_t bInterfaceSubClass; /*!< interface subclass ID */ uint8_t bInterfaceProtocol; /*!< interface protocol ID */ uint8_t iInterface; /*!< index of the string descriptor describing the interface */ } usb_desc_itf; |
- 端點描述符
端點描述符用以描述端點信息,端點描述符不能單獨返回,必須附著在配置描述符后一并返回,其相關結構體定義如下。
- 字符串描述符
字符串描述符可含有指向描述制造商、產品、序列號、配置和接口的字符串的索引。類和制造商專屬描述符可含有指向額外字符串描述符的索引。對字符串描述符的支持是可選的,有些類可能會需要它們。
C typedef struct _usb_desc_str { usb_desc_header header; /*!< descriptor header, including type and size. */ uint16_t unicode_string[64]; /*!< unicode string data */ } usb_desc_str; |
13.2.1.5USB枚舉過程
USB枚舉實際上是host檢測到device插入后,通過發(fā)送各種標準請求,請device返回各種USB描述符的過程。USB枚舉的示意圖如下:
13.2.2GD32 USBD模塊簡介
GD32F4xx系列MCU提供了一個USB2.0全速USBFS OTG接口模塊和一個USB2.0高速USBHS接口模塊,其中,USBHS若需要使用高速接口,需要外接USBHS高速PHY芯片,如果不外借高速PHY,其可以作為USBFS接口,因而GD32F4XX在不接外部高速PHY的情況下,其可以使用兩個USBFS接口。
USBFS支持USB 2.0協(xié)議所定義的四種傳輸類型(控制、批量、中斷和同步傳輸),本開發(fā)板使用的是USBFS接口,以下為大家介紹USBFS接口功能。
USBFS主要特性如下:
n支持USB 2.0全速(12Mb/s)/低速(1.5Mb/s)主機模式;
n支持USB 2.0全速(12Mb/s) 設備模式;
n支持遵循HNP(主機協(xié)商協(xié)議)和SRP(會話請求協(xié)議)的OTG協(xié)議;
n支持所有的4種傳輸方式:控制傳輸、批量傳輸、中斷傳輸和同步傳輸;
n在主機模式下,包含USB事務調度器,用于有效地處理USB事務請求;
n包含一個1.25KB的FIFO RAM;
n在主機模式下,支持8個通道;
n在主機模式下,包含2個發(fā)送FIFO(周期性發(fā)送FIFO和非周期性發(fā)送FIFO)和1個接收
FIFO(由所有的通道共享);
n在設備模式下,包含4個發(fā)送FIFO(每個IN端點一個發(fā)送FIFO)和1個接收FIFO(由所有
的OUT端點共享);
n在設備模式下, 支持4個OUT端點和4個IN端點;
n在設備模式下,支持遠程喚醒功能;
n包含一個支持USB協(xié)議的全速USB PHY;
n在主機模式下,SOF的時間間隔可動態(tài)調節(jié);
n可將SOF脈沖輸出到PAD;
n可檢測ID引腳電平和VBUS電壓;
n在主機模式或者OTG A設備模式下,需要外部部件為連接的USB設備提供電源。
USBD模塊框圖如下所示。
13.2.3USBFS固件庫說明
USBFS 固件庫使用指南可以參考官網相關文檔,下載地址如下:https://www.gd32mcu.com/download/down/document_id/372/path_type/2
13.3硬件設計
GD32F470紫藤派開發(fā)板的USB通信接口選擇的是目前較為通用的Type C接口,讀者手中的用于手機充電的Type C通信線即可使用。
13.4代碼解析
本例程主要實現(xiàn)通過按鍵向PC發(fā)送鍵值的現(xiàn)象,實現(xiàn)模擬鍵盤的效果。
本例程主函數如下所示。
C int main(void) { usb_gpio_config(); usb_rcu_config(); usb_timer_init(); hid_itfop_register (&hid_keyboard, &fop_handler); usbd_init (&hid_keyboard, #ifdef USE_USB_FS USB_CORE_ENUM_FS, #elif defined(USE_USB_HS) USB_CORE_ENUM_HS, #endif &hid_desc, &usbd_hid_cb); usb_intr_config(); /* check if USB device is enumerated successfully */ while (USBD_CONFIGURED != hid_keyboard.dev.cur_status) { } while (1) { fop_handler.hid_itf_data_process(&hid_keyboard); } } |
gpio的配置如下,定義為全速模式,主要配置PA11和PA12引腳。
C void usb_gpio_config(void) { rcu_periph_clock_enable(RCU_SYSCFG); #ifdef USE_USB_FS rcu_periph_clock_enable(RCU_GPIOA); /* USBFS_DM(PA11) and USBFS_DP(PA12) GPIO pin configuration */ gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11 | GPIO_PIN_12); gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_11 | GPIO_PIN_12); gpio_af_set(GPIOA, GPIO_AF_10, GPIO_PIN_11 | GPIO_PIN_12); #elif defined(USE_USB_HS) #ifdef USE_ULPI_PHY rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_GPIOC); rcu_periph_clock_enable(RCU_GPIOH); rcu_periph_clock_enable(RCU_GPIOI); /* ULPI_STP(PC0) GPIO pin configuration */ gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_0); gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_0); /* ULPI_CK(PA5) GPIO pin configuration */ gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_5); gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_5); /* ULPI_NXT(PH4) GPIO pin configuration */ gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_4); gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_4); /* ULPI_DIR(PI11) GPIO pin configuration */ gpio_mode_set(GPIOI, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11); gpio_output_options_set(GPIOI, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_11); /* ULPI_D1(PB0), ULPI_D2(PB1), ULPI_D3(PB10), ULPI_D4(PB11) \ ULPI_D5(PB12), ULPI_D6(PB13) and ULPI_D7(PB5) GPIO pin configuration */ gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, \ GPIO_PIN_5 | GPIO_PIN_13 | GPIO_PIN_12 |\ GPIO_PIN_11 | GPIO_PIN_10 | GPIO_PIN_1 | GPIO_PIN_0); gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, \ GPIO_PIN_5 | GPIO_PIN_13 | GPIO_PIN_12 |\ GPIO_PIN_11 | GPIO_PIN_10 | GPIO_PIN_1 | GPIO_PIN_0); /* ULPI_D0(PA3) GPIO pin configuration */ gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_3); gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_3); gpio_af_set(GPIOC, GPIO_AF_10, GPIO_PIN_0); gpio_af_set(GPIOH, GPIO_AF_10, GPIO_PIN_4); gpio_af_set(GPIOI, GPIO_AF_10, GPIO_PIN_11); gpio_af_set(GPIOA, GPIO_AF_10, GPIO_PIN_5 | GPIO_PIN_3); gpio_af_set(GPIOB, GPIO_AF_10, GPIO_PIN_5 | GPIO_PIN_13 | GPIO_PIN_12 |\ GPIO_PIN_11 | GPIO_PIN_10 | GPIO_PIN_1 | GPIO_PIN_0); #elif defined(USE_EMBEDDED_PHY) rcu_periph_clock_enable(RCU_GPIOB); /* USBHS_DM(PB14) and USBHS_DP(PB15) GPIO pin configuration */ gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_14 | GPIO_PIN_15); gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_14 | GPIO_PIN_15); gpio_af_set(GPIOB, GPIO_AF_12, GPIO_PIN_14 | GPIO_PIN_15); #endif /* USE_ULPI_PHY */ #endif /* USE_USBFS */ } |
rcu的配置如下,主要用于配置USB時鐘,USB需要一個穩(wěn)定的48M時鐘。
C void usb_rcu_config(void) { #ifdef USE_USB_FS rcu_pll48m_clock_config(RCU_PLL48MSRC_PLLQ); rcu_ck48m_clock_config(RCU_CK48MSRC_PLL48M); rcu_periph_clock_enable(RCU_USBFS); #elif defined(USE_USB_HS) #ifdef USE_EMBEDDED_PHY rcu_pll48m_clock_config(RCU_PLL48MSRC_PLLQ); rcu_ck48m_clock_config(RCU_CK48MSRC_PLL48M); #elif defined(USE_ULPI_PHY) rcu_periph_clock_enable(RCU_USBHSULPI); #endif /* USE_EMBEDDED_PHY */ rcu_periph_clock_enable(RCU_USBHS); #endif /* USB_USBFS */ } |
Usb timer的配置如下,主要用于延遲。
C void usb_timer_init (void) { /* configure the priority group to 2 bits */ nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); /* enable the TIMER2 global interrupt */ nvic_irq_enable((uint8_t)TIMER2_IRQn, 1U, 0U); rcu_periph_clock_enable(RCU_TIMER2); } |
注冊HID接口操作函數如下所示。在該代碼清單中,注冊了HID接口操作的配置以及數據處理函數句柄,用于后續(xù)函數調用。
C uint8_t hid_itfop_register (usb_dev *udev, hid_fop_handler *hid_fop) { if (NULL != hid_fop) { udev->dev.user_data = (void *)hid_fop; return USBD_OK; } return USBD_FAIL; } |
USBD內核初始化函數如下所示。在該代碼清單中,首先配置設備類callback函數,之后創(chuàng)建字符串,配置USB以及初始化USB內核,斷開USB連接,初始化USB設備模式,之后設置USB連接,將USB連接狀態(tài)配置為DEFAULT默認狀態(tài),啟動狀態(tài)機。
C void usbd_init (usb_core_driver *udev, usb_core_enum core, usb_desc *desc, usb_class_core *class_core) { udev->dev.desc = desc; /* class callbacks */ udev->dev.class_core = class_core; /* create serial string */ serial_string_get(udev->dev.desc->strings[STR_IDX_SERIAL]); /* configure USB capabilities */ (void)usb_basic_init (&udev->bp, &udev->regs, core); usb_globalint_disable(&udev->regs); /* initializes the USB core*/ (void)usb_core_init (udev->bp, &udev->regs); /* set device disconnect */ usbd_disconnect (udev); #ifndef USE_OTG_MODE usb_curmode_set(&udev->regs, DEVICE_MODE); #endif /* USE_OTG_MODE */ /* initializes device mode */ (void)usb_devcore_init (udev); usb_globalint_enable(&udev->regs); /* set device connect */ usbd_connect (udev); udev->dev.cur_status = (uint8_t)USBD_DEFAULT; } |
配置USB中斷函數如下。
C void usb_intr_config(void) { nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); #ifdef USE_USB_FS nvic_irq_enable((uint8_t)USBFS_IRQn, 3U, 0U); #if USBFS_LOW_POWER /* enable the power module clock */ rcu_periph_clock_enable(RCU_PMU); /* USB wakeup EXTI line configuration */ exti_interrupt_flag_clear(EXTI_18); exti_init(EXTI_18, EXTI_INTERRUPT, EXTI_TRIG_RISING); exti_interrupt_enable(EXTI_18); nvic_irq_enable((uint8_t)USBFS_WKUP_IRQn, 0U, 0U); #endif /* USBFS_LOW_POWER */ #elif defined(USE_USB_HS) nvic_irq_enable((uint8_t)USBHS_IRQn, 3U, 0U); #if USBHS_LOW_POWER /* enable the power module clock */ rcu_periph_clock_enable(RCU_PMU); /* USB wakeup EXTI line configuration */ exti_interrupt_flag_clear(EXTI_20); exti_init(EXTI_20, EXTI_INTERRUPT, EXTI_TRIG_RISING); exti_interrupt_enable(EXTI_20); nvic_irq_enable((uint8_t)USBHS_WKUP_IRQn, 0U, 0U); #endif /* USBHS_LOW_POWER */ #endif /* USE_USB_FS */ #ifdef USB_HS_DEDICATED_EP1_ENABLED nvic_irq_enable(USBHS_EP1_Out_IRQn, 1, 0); nvic_irq_enable(USBHS_EP1_In_IRQn, 1, 0); #endif /* USB_HS_DEDICATED_EP1_ENABLED */ } |
內部上拉電阻被上拉后,主機將會對設備進行枚舉,設備端采用while (USBD_CONFIGURED != hid_keyboard.dev.cur_status) 語句進行等待。當USB設備狀態(tài)變?yōu)閁SBD_CONFIGURED狀態(tài)時,表明設備枚舉完成。
枚舉完成之后,程序將進入主循環(huán)中,在主循環(huán)中,循環(huán)調用HID USB模擬鍵盤數據處理函數,在該函數中,首先判斷上次傳輸是否完成,完成之后通過掃描按鍵的方式查看按鍵是否被按下,若按鍵被按下,則通過hid_report_send()函數發(fā)送鍵盤報告數據。
C static void hid_key_data_send(usb_dev *udev) { standard_hid_handler *hid = (standard_hid_handler *)udev->dev.class_data[USBD_HID_INTERFACE]; if (hid->prev_transfer_complete) { switch (key_state()) { case CHAR_A: hid->data[2] = 0x04U; break; case CHAR_B: hid->data[2] = 0x05U; break; default: break; } if (0U != hid->data[2]) { hid_report_send(udev, hid->data, HID_IN_PACKET); } } } |
報文發(fā)送函數定義如下,該函數包含三個參數,udev為初始化后的設備操作結構體;report為發(fā)送報告緩沖區(qū)地址;len為發(fā)送報告的長度。在該函數中,如果設備已經被枚舉成功,則首先將prev_transfer_complete標志位設置為0,表明接下來將進行發(fā)送數據,數據并未發(fā)送完成,之后,調用usbd_ep_send()將需要發(fā)送的報告拷貝到USB外設緩沖區(qū)中并設置端點為有效狀態(tài),等待主機發(fā)送IN令牌包,USB設備將外設緩沖區(qū)中的數據發(fā)送給主機。
C uint8_t hid_report_send (usb_dev *udev, uint8_t *report, uint32_t len) { standard_hid_handler *hid = (standard_hid_handler *)udev->dev.class_data[USBD_HID_INTERFACE]; hid->prev_transfer_complete = 0U; usbd_ep_send(udev, HID_IN_EP, report, len); return USBD_OK; } |
當數據發(fā)送完成,USB設備將調用hid_data_in()函數進行數據處理。該函數程序如下所示。在該函數中,首先判斷hid->data[2]的數據是否為0x00,如果不為0x00表明上次發(fā)送的為按鍵按下的鍵值,還需發(fā)送按鍵松開的鍵值,如果為0x00表明上次按鍵按下和松開的鍵值均已發(fā)送完成,之后將prev_transfer_complete設置為1,表明上一次的按鍵數據傳輸完成,可進行下次按鍵數據傳輸。
C static uint8_t hid_data_in (usb_dev *udev, uint8_t ep_num) { standard_hid_handler *hid = (standard_hid_handler *)udev->dev.class_data[USBD_HID_INTERFACE]; if (0U != hid->data[2]) { hid->data[2] = 0x00U; usbd_ep_send(udev, HID_IN_EP, hid->data, HID_IN_PACKET); } else { hid->prev_transfer_complete = 1U; } return USBD_OK; } |
在該例程中通過hid->prev_transfer_complete數據流程標志位進行數據發(fā)送控制,讀者可使用該標志位用于對數據發(fā)送的控制,當該標志位為0的時候,表明數據已被填送到USB緩沖區(qū),但還沒有發(fā)送給主機,此時MCU不能繼續(xù)調用發(fā)送函數向緩沖區(qū)中填數據,否則可能導致數據覆蓋丟失,正確做法是等待該標志位置位,表明上一包數據已被主機讀取,然后再繼續(xù)發(fā)送后續(xù)數據。 |
13.5實驗結果
將本例程燒錄到紫藤派開發(fā)板中,通過Type C數據線連接開發(fā)板和PC,之后分別按下WKUP和USER按鍵,將會向PC發(fā)送A、B鍵值。
本教程由GD32 MCU方案商聚沃科技原創(chuàng)發(fā)布,了解更多GD32 MCU教程,關注聚沃科技官網
-
單片機
+關注
關注
6035文章
44554瀏覽量
634653 -
usb
+關注
關注
60文章
7936瀏覽量
264482 -
開發(fā)板
+關注
關注
25文章
5032瀏覽量
97375 -
虛擬鍵盤
+關注
關注
0文章
11瀏覽量
7827 -
GD32
+關注
關注
7文章
403瀏覽量
24328
發(fā)布評論請先 登錄
相關推薦
評論