
27.1實驗內(nèi)容
通過本實驗主要學習以下內(nèi)容:
27.2實驗原理
27.2.1USB通信基礎知識
USB的全稱是Universal Serial Bus,通用串行總線。它的出現(xiàn)主要是為了簡化個人計算機與外圍設備的連接,增加易用性。USB支持熱插拔,并且是即插即用的,另外,它還具有很強的可擴展性,傳輸速度也很快,這些特性使支持USB接口的電子設備更易用、更大眾化。GD32F303系列MCU集成了USB2.0全速設備USBD模塊,可以滿足作為USB設備與主機通信的需求。首先為大家介紹USB通信的一些基礎知識,包括USB協(xié)議、枚舉流程等,建議讀者可以多多閱讀USB協(xié)議,以更深入了解USB,USB官網(wǎng)鏈接如下,可參考:https://www.usb.org/
27.2.1.1USB金字塔型拓撲結(jié)構
塔頂為USB主控制器和根集線器(Root Hub),下面接USB集線器(Hub),集線器將一個USB口擴展為多個USB口,USB2.0規(guī)定集線器的層數(shù)最多為6層,理論上一個USB主控制器最多可接127個設備,因為協(xié)議規(guī)定USB設備具有一個7 bit的地址(取值范圍為0~127,而地址0是保留給未初始化的設備使用的)。

27.2.1.2NRZI編碼
USB采用差分信號傳輸,使用的是如上圖所示的NRZI編碼方式:數(shù)據(jù)為0時,電平翻轉(zhuǎn);數(shù)據(jù)為1時,電平不翻轉(zhuǎn)。如果出現(xiàn)6個連續(xù)的數(shù)據(jù)1,則插入一個數(shù)據(jù)0,強制電平翻轉(zhuǎn),以便時鐘同步。上面的一條線表示的是原始數(shù)據(jù)序列,下面的一條線表示的是經(jīng)過NRZI編碼后的數(shù)據(jù)序列。

27.2.1.3USB數(shù)據(jù)協(xié)議
USB數(shù)據(jù)是由二進制數(shù)據(jù)串組成,首先由數(shù)據(jù)串構成包(packet),包再構成事務(transaction),事務最終構成傳輸(transfer)。
USB傳輸?shù)淖钚挝粸榘粋€包被分成不同的域,根據(jù)不同類型的包,所包含的域是不一樣的,但是不同的包有個共同的特點,就是以包起始(SOP)開始,之后是同步域(0x00000001),然后是包內(nèi)容,最后以包結(jié)束符(EOP)結(jié)束這個包。PID為標識域,由四位標識符加4位標識符反碼構成,表明包的類型和格式。根據(jù)PID的不同,USB協(xié)議中規(guī)定的包類型有令牌包、數(shù)據(jù)包、握手包和特殊包等。

USB事務通常有兩個或三個包組成:令牌包、數(shù)據(jù)包和握手包,令牌包用來啟動一個事務,總是由主機發(fā)送;數(shù)據(jù)包用來傳輸數(shù)據(jù);握手包由數(shù)據(jù)接收者進行發(fā)送,表明數(shù)據(jù)的接收情況。批量、同步和中斷傳輸每次傳輸都是一個事務,控制傳輸包括三個階段:建立過程、數(shù)據(jù)過程和狀態(tài)過程。
針對不同的數(shù)據(jù)傳輸場景,USB分為四種數(shù)據(jù)傳輸模式,這四種傳輸模式分別由不同的包(packet)組成,并且有不同的數(shù)據(jù)處理策略。每種數(shù)據(jù)傳輸模式的流程示意圖以及應用場景如下:
- 控制傳輸一般用于命令和狀態(tài)的傳輸,分為控制讀、控制寫和無數(shù)據(jù)控制傳輸。在設備枚舉的過程中,采用控制傳輸方式進行數(shù)據(jù)傳輸。

- 批量傳輸分為批量讀和批量寫,用于數(shù)據(jù)量大、對實時性要求不高的場合,如U盤。

- 中斷傳輸用于數(shù)據(jù)量小的場合,保證查詢頻率,如鼠標、鍵盤。

- 同步傳輸用于數(shù)據(jù)量大、同時對實時性要求較高的場合,如音視頻。不保證數(shù)據(jù)完整性,沒有ACK/NAK應答包,不進行數(shù)據(jù)重傳。

27.2.1.4USB描述符

- 一個USB設備通常有一個或多個配置,但在同一時刻只能有一個配置;
- 一個配置通常有一個或多個接口;
- 一個接口通常有一個或多個端點;
在USB通信中,USB設備需要配置多個USB描述符用以枚舉階段將描述符返回給主機,用以主機的枚舉以及識別。USB描述符包括設備描述符、配置描述符、接口描述符、端點描述符以及字符串描述符等。在GD32 USBD固件庫中,針對各種描述符都按照USB協(xié)議定義了相關結(jié)構體,具體說明如下。
- 設備描述符
每個設備必須有一個設備描述符,設備描述符提供了關于設備的配置、設備所歸屬的類、設備所遵循的協(xié)議代碼、VID、PID等信息,其相關結(jié)構體定義如下。
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ī)定了該設備有多少種配置,每種配置都有一個描述符,其相關結(jié)構體定義如下。
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; |
- 接口描述符
接口描述符用以描述接口信息,接口描述符不能單獨返回,必須附著在配置描述符后一并返回,其相關結(jié)構體定義如下。
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; |
- 端點描述符
端點描述符用以描述端點信息,端點描述符不能單獨返回,必須附著在配置描述符后一并返回,其相關結(jié)構體定義如下。
C typedef struct _usb_desc_ep { usb_desc_header header; /*!< descriptor header, including type and size */ uint8_t bEndpointAddress; /*!< logical address of the endpoint */ uint8_t bmAttributes; /*!< endpoint attribute */ uint16_t wMaxPacketSize; /*!< size of the endpoint bank, in bytes */ uint8_t bInterval; /*!< polling interval in milliseconds for the endpoint if it is an INTERRUPT or ISOCHRONOUS type */ } usb_desc_ep; |
- 字符串描述符
字符串描述符可含有指向描述制造商、產(chǎn)品、序列號、配置和接口的字符串的索引。類和制造商專屬描述符可含有指向額外字符串描述符的索引。對字符串描述符的支持是可選的,有些類可能會需要它們。
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; |
27.2.1.5USB枚舉過程
USB枚舉實際上是host檢測到device插入后,通過發(fā)送各種標準請求,請device返回各種USB描述符的過程。USB枚舉的示意圖如下:

27.2.2GD32 USBD模塊簡介
GD32F303系列MCU提供了一個USB2.0全速USBD接口模塊,它內(nèi)部包含了一個USB物理層而不需要額外的外部物理層芯片。USBD支持USB 2.0協(xié)議所定義的四種傳輸類型(控制、批量、中斷和同步傳輸)。
主要特性如下:
? USB 2.0全速設備控制器;
?最多支持8個可配置的端點;
?支持雙緩沖的批量傳輸端點/同步傳輸端點;
?支持USB 2.0鏈接電源管理;
?每個端點都支持控制,批量,同步或中斷傳輸(端點0除外,端點0只支持控制傳輸) ;
?支持USB掛起/恢復操作;
?與CAN共享512字節(jié)的專用SRAM用于數(shù)據(jù)緩沖;
?集成的USB物理層。
USBD模塊框圖如下所示。

27.2.3USBD固件庫說明
USBD固件庫框圖如下所示。用戶應用程序(User application)調(diào)用GD32全速USB設備固件庫中的接口實現(xiàn)USB設備與主機之間的通信,架構的最底層為GD32 MCU開發(fā)板的硬件。其中,GD32全速USB設備固件庫(GD32F30x_usbd_Library)分為兩層,頂層為應用接口層,用戶可以修改,包含main.c和USB相關設備類驅(qū)動;底層為USBD設備驅(qū)動層,不建議用戶修改,該驅(qū)動層包含實現(xiàn)USB通信相關協(xié)議以及USBD底層模塊操作。

USBD_Drivers設備驅(qū)動層(Firmware\GD32F30x_usbd_library\usbd)包含兩個文件夾,分別為Include和Source,其中,Include為底層頭文件,Source為底層源文件。

其中,usbd_lld_core.h/c文件中的庫函數(shù)說明如下所示。

usbd_lld_int.h/.c文件中的庫函數(shù)說明如下所示。

USBD_Device設備驅(qū)動層(Firmware\GD32F30x_usbd_library\device)包含兩個文件夾,分別為Include和Source,其中,Include為底層頭文件,Source為底層源文件。

其中,usbd_core.h/.c文件中的庫函數(shù)說明如下所示。

usbd_enum.h/.c文件中的庫函數(shù)說明如下所示。

usbd_pwr.h/.c文件中的庫函數(shù)說明如下所示。

usbd_transc.h/.c文件中的庫函數(shù)說明如下所示。

27.3硬件設計
GD32F303紅楓派開發(fā)板的USB通信接口選擇的是目前較為通用的Type C接口,讀者手中的用于手機充電的Type C通信線即可使用。
USB的DP和DM線上使用22歐姆串阻,DP線通過1.5K電阻上拉到USBFS_CTL控制引腳,該引腳使用的是PD3引腳。

27.4代碼解析
本例程主要實現(xiàn)通過按鍵向PC發(fā)送鍵值的現(xiàn)象,實現(xiàn)模擬鍵盤的效果。
本例程主函數(shù)如下所示,首先配置延遲初始化,歷程中使用到了ms延遲,之后配置rcu、gpio、usbd、NVIC等相關外設,具體說明將在后續(xù)介紹。
C int main(void) { delay_init(); /* system clocks configuration */ rcu_config(); /* GPIO configuration */ gpio_config(); hid_itfop_register (&usb_hid, &fop_handler); /* USB device configuration */ usbd_init(&usb_hid, &hid_desc, &hid_class); /* NVIC configuration */ nvic_config(); usbd_connect(&usb_hid); while(USBD_CONFIGURED != usb_hid.cur_status){ } while (1) { fop_handler.hid_itf_data_process(&usb_hid); } } |
rcu的配置如下,主要用于配置USB時鐘,USB需要一個穩(wěn)定的48M時鐘,一般可通過系統(tǒng)時鐘分頻獲取,由于有固定的分頻系數(shù),所以系統(tǒng)時鐘一般選擇48M、72M、96M或120M,歷程中做了自動分頻適配。另外如果使用IRC48Mhz時鐘作為USB時鐘,系統(tǒng)時鐘大于24MHz即可。
C void rcu_config(void) { uint32_t system_clock = rcu_clock_freq_get(CK_SYS); /* enable USB pull-up pin clock */ rcu_periph_clock_enable(RCU_AHBPeriph_GPIO_PULLUP); if (48000000U == system_clock) { rcu_usb_clock_config(RCU_CKUSB_CKPLL_DIV1); } else if (72000000U == system_clock) { rcu_usb_clock_config(RCU_CKUSB_CKPLL_DIV1_5); } else if (96000000U == system_clock) { rcu_usb_clock_config(RCU_CKUSB_CKPLL_DIV2); } else if (120000000U == system_clock) { rcu_usb_clock_config(RCU_CKUSB_CKPLL_DIV2_5); } else { /* reserved */ } /* enable USB APB1 clock */ rcu_periph_clock_enable(RCU_USBD); } |
gpio配置主要用于配置DP線的上拉電阻,dp線上拉主要用于控制USB設備接入主機的時機。本例程中使用PD3的引腳。
若讀者的硬件使用其他的引腳作為dp線的上拉控制,修改上拉引腳的宏定義配置即可。 |
C void gpio_config(void) { /* configure usb pull-up pin */ gpio_init(USB_PULLUP, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, USB_PULLUP_PIN); /* USB wakeup EXTI line configuration */ exti_interrupt_flag_clear(EXTI_18); exti_init(EXTI_18, EXTI_INTERRUPT, EXTI_TRIG_RISING); } #define USB_PULLUP GPIOD #define USB_PULLUP_PIN GPIO_PIN_3 #define RCU_AHBPeriph_GPIO_PULLUP RCU_GPIOD |
注冊HID接口操作函數(shù)如下所示。在該代碼清單中,注冊了HID接口操作的配置以及數(shù)據(jù)處理函數(shù)句柄,用于后續(xù)函數(shù)調(diào)用。
C uint8_t hid_itfop_register (usb_dev *udev, hid_fop_handler *hid_fop) { if (NULL != hid_fop) { udev->user_data = (void *)hid_fop; return USBD_OK; } return USBD_FAIL; } |
USBD內(nèi)核初始化函數(shù)如下所示。在該代碼清單中,首先配置USB內(nèi)核基本屬性參數(shù),然后初始化USBD描述符、設備類內(nèi)核以及設備類處理函數(shù)指針,之后初始化端點事務函數(shù)數(shù)組,配置電源管理以及USB掛起狀態(tài)使能,最后調(diào)用設備類內(nèi)核初始化函數(shù)完成USBD內(nèi)核初始化。
C void usbd_init (usb_dev *udev, usb_desc *desc, usb_class *usbc) { /* configure USBD core basic attributes */ usbd_core.basic.max_ep_count = 8U; usbd_core.basic.twin_buf = 1U; usbd_core.basic.ram_size = 512U; usbd_core.dev = udev; udev->desc = desc; udev->class_core = usbc; udev->drv_handler = &usbd_drv_handler; udev->ep_transc[0][TRANSC_SETUP] = _usb_setup_transc; udev->ep_transc[0][TRANSC_OUT] = _usb_out0_transc; udev->ep_transc[0][TRANSC_IN] = _usb_in0_transc; /* configure power management */ udev->pm.power_mode = (udev->desc->config_desc[7] & 0x40U) >> 5; /* enable USB suspend */ udev->pm.suspend_enabled = 1U; /* USB low level initialization */ udev->drv_handler->init(); /* create serial string */ serial_string_get((uint16_t *)udev->desc->strings[STR_IDX_SERIAL]); } |
NVIC配置函數(shù)如下所示。在該代碼清單中首先對NVIC分組進行配置,其中1位用于搶占優(yōu)先級,3位用于次優(yōu)先級。之后使能USBD低優(yōu)先級中斷和喚醒中斷。
C void nvic_config(void) { /* 2 bits for preemption priority, 2 bits for subpriority */ nvic_priority_group_set(NVIC_PRIGROUP_PRE1_SUB3); /* enable the USB low priority interrupt */ nvic_irq_enable((uint8_t)USBD_LP_CAN0_RX0_IRQn, 1U, 0U); /* enable the USB Wake-up interrupt */ nvic_irq_enable((uint8_t)USBD_WKUP_IRQn, 0U, 0U); } |
然后調(diào)用usbd_connect(&usb_hid);函數(shù)將上拉引腳電平進行上拉,并將USB設備狀態(tài)udev->cur_status設置為連接狀態(tài)USBD_CONNECTED。
C __STATIC_INLINE void usbd_connect (usb_dev *udev) { udev->drv_handler->dp_pullup(SET); udev->cur_status = (uint8_t)USBD_CONNECTED; } |
上拉電阻被上拉后,主機將會對設備進行枚舉,設備端采用while(USBD_CONFIGURED != usb_hid.cur_status)語句進行等待。當USB設備狀態(tài)變?yōu)閁SBD_CONFIGURED狀態(tài)時,表明設備枚舉完成。
枚舉完成之后,程序?qū)⑦M入主循環(huán)中,在主循環(huán)中,循環(huán)調(diào)用HID USB模擬鍵盤數(shù)據(jù)處理函數(shù),在該函數(shù)中,首先判斷上次傳輸是否完成,完成之后通過掃描按鍵的方式查看按鍵是否被按下,若按鍵被按下,則通過hid_report_send()函數(shù)發(fā)送鍵盤報告數(shù)據(jù)。
C static void hid_key_data_send(usb_dev *udev) { standard_hid_handler *hid = (standard_hid_handler *)udev->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; case CHAR_C: hid->data[2] = 0x06U; break; default: break; } if (0U != hid->data[2]) { hid_report_send(udev, hid->data, HID_IN_PACKET); } } } |
報文發(fā)送函數(shù)定義如下,該函數(shù)包含三個參數(shù),udev為初始化后的設備操作結(jié)構體;report為發(fā)送報告緩沖區(qū)地址;len為發(fā)送報告的長度。在該函數(shù)中,如果設備已經(jīng)被枚舉成功,則首先將prev_transfer_complete標志位設置為0,表明接下來將進行發(fā)送數(shù)據(jù),數(shù)據(jù)并未發(fā)送完成,之后,調(diào)用usbd_ep_send()將需要發(fā)送的報告拷貝到USB外設緩沖區(qū)中并設置端點為有效狀態(tài),等待主機發(fā)送IN令牌包,USB設備將外設緩沖區(qū)中的數(shù)據(jù)發(fā)送給主機。
C uint8_t hid_report_send (usb_dev *udev, uint8_t *report, uint16_t len) { standard_hid_handler *hid = (standard_hid_handler *)udev->class_data[USBD_HID_INTERFACE]; /* check if USB is configured */ hid->prev_transfer_complete = 0U; usbd_ep_send(udev, HID_IN_EP, report, len); return USBD_OK; } |
當數(shù)據(jù)發(fā)送完成,USB設備將調(diào)用hid_data_in_handler()函數(shù)進行數(shù)據(jù)處理。該函數(shù)程序如下所示。在該函數(shù)中,首先判斷hid->data[2]的數(shù)據(jù)是否為0x00,如果不為0x00表明上次發(fā)送的為按鍵按下的鍵值,還需發(fā)送按鍵松開的鍵值,如果為0x00表明上次按鍵按下和松開的鍵值均已發(fā)送完成,之后將prev_transfer_complete設置為1,表明上一次的按鍵數(shù)據(jù)傳輸完成,可進行下次按鍵數(shù)據(jù)傳輸。
C static void hid_data_in_handler (usb_dev *udev, uint8_t ep_num) { standard_hid_handler *hid = (standard_hid_handler *)udev->class_data[USBD_HID_INTERFACE]; if (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; } } |
在該例程中通過hid->prev_transfer_complete數(shù)據(jù)流程標志位進行數(shù)據(jù)發(fā)送控制,讀者可使用該標志位用于對數(shù)據(jù)發(fā)送的控制,當該標志位為0的時候,表明數(shù)據(jù)已被填送到USB緩沖區(qū),但還沒有發(fā)送給主機,此時MCU不能繼續(xù)調(diào)用發(fā)送函數(shù)向緩沖區(qū)中填數(shù)據(jù),否則可能導致數(shù)據(jù)覆蓋丟失,正確做法是等待該標志位置位,表明上一包數(shù)據(jù)已被主機讀取,然后再繼續(xù)發(fā)送后續(xù)數(shù)據(jù)。 |
27.5實驗結(jié)果
將本例程燒錄到紅楓派開發(fā)板中,通過Type C數(shù)據(jù)線連接開發(fā)板和PC,之后按下ROCKER_KEY、K1、K2按鍵,將會向PC發(fā)送A、B、C鍵值。

本教程由GD32 MCU方案商聚沃科技原創(chuàng)發(fā)布,了解更多GD32 MCU教程,關注聚沃科技官網(wǎng)
-
單片機
+關注
關注
6061文章
44866瀏覽量
645999 -
嵌入式
+關注
關注
5133文章
19500瀏覽量
314194 -
彩燈
+關注
關注
4文章
67瀏覽量
34431 -
開發(fā)板
+關注
關注
25文章
5471瀏覽量
101702 -
Timer
+關注
關注
1文章
64瀏覽量
13095
發(fā)布評論請先 登錄
【GD32F303紅楓派開發(fā)板使用手冊】第二十講八 USB-虛擬串口實驗

【星空派GD32F303開發(fā)板試用體驗】開發(fā)記錄匯總
【星空派GD32F303開發(fā)板試用體驗】開箱+環(huán)境搭建
【星空派GD32F303開發(fā)板試用體驗】開箱+環(huán)境搭建
【星空派GD32F303開發(fā)板試用體驗】+板卡概覽
第二十七講 同步時序邏輯電路的設計

STM32CUBEMX開發(fā)GD32F303(14)----IIC之配置OLED

GD32F303固件庫開發(fā)

【GD32F303紅楓派開發(fā)板使用手冊】第二講 GPIO-流水燈實驗

【GD32F303紅楓派開發(fā)板使用手冊】第三講 GPIO-按鍵查詢檢測實驗

【GD32F303紅楓派開發(fā)板使用手冊】第五講 FMC-片內(nèi)Flash擦寫讀實驗

【GD32F303紅楓派開發(fā)板使用手冊】第十六講 USART-DMA串口收發(fā)實驗

【GD32F303紅楓派開發(fā)板使用手冊】第二十講 SPI-SPI NAND FLASH讀寫實驗

評論