13.1實(shí)驗(yàn)內(nèi)容
通過本實(shí)驗(yàn)主要學(xué)習(xí)以下內(nèi)容:
13.2實(shí)驗(yàn)原理
13.2.1USB通信基礎(chǔ)知識(shí)
USB的全稱是Universal Serial Bus,通用串行總線。它的出現(xiàn)主要是為了簡化個(gè)人計(jì)算機(jī)與外圍設(shè)備的連接,增加易用性。USB支持熱插拔,并且是即插即用的,另外,它還具有很強(qiáng)的可擴(kuò)展性,傳輸速度也很快,這些特性使支持USB接口的電子設(shè)備更易用、更大眾化。GD32H7系列MCU集成了USB2.0高速OTG模塊。首先為大家介紹USB通信的一些基礎(chǔ)知識(shí),包括USB協(xié)議、枚舉流程等,建議讀者可以多多閱讀USB協(xié)議,以更深入了解USB,USB官網(wǎng)鏈接如下,可參考:https://www.usb.org/
13.2.1.1USB金字塔型拓?fù)浣Y(jié)構(gòu)
塔頂為USB主控制器和根集線器(Root Hub),下面接USB集線器(Hub),集線器將一個(gè)USB口擴(kuò)展為多個(gè)USB口,USB2.0規(guī)定集線器的層數(shù)最多為6層,理論上一個(gè)USB主控制器最多可接127個(gè)設(shè)備,因?yàn)閰f(xié)議規(guī)定USB設(shè)備具有一個(gè)7 bit的地址(取值范圍為0~127,而地址0是保留給未初始化的設(shè)備使用的)。
13.2.1.2NRZI編碼
USB采用差分信號(hào)傳輸,使用的是如上圖所示的NRZI編碼方式:數(shù)據(jù)為0時(shí),電平翻轉(zhuǎn);數(shù)據(jù)為1時(shí),電平不翻轉(zhuǎn)。如果出現(xiàn)6個(gè)連續(xù)的數(shù)據(jù)1,則插入一個(gè)數(shù)據(jù)0,強(qiáng)制電平翻轉(zhuǎn),以便時(shí)鐘同步。上面的一條線表示的是原始數(shù)據(jù)序列,下面的一條線表示的是經(jīng)過NRZI編碼后的數(shù)據(jù)序列。
13.2.1.3USB數(shù)據(jù)協(xié)議
USB數(shù)據(jù)是由二進(jìn)制數(shù)據(jù)串組成,首先由數(shù)據(jù)串構(gòu)成包(packet),包再構(gòu)成事務(wù)(transaction),事務(wù)最終構(gòu)成傳輸(transfer)。
USB傳輸?shù)淖钚挝粸榘?,一個(gè)包被分成不同的域,根據(jù)不同類型的包,所包含的域是不一樣的,但是不同的包有個(gè)共同的特點(diǎn),就是以包起始(SOP)開始,之后是同步域(0x00000001),然后是包內(nèi)容,最后以包結(jié)束符(EOP)結(jié)束這個(gè)包。PID為標(biāo)識(shí)域,由四位標(biāo)識(shí)符加4位標(biāo)識(shí)符反碼構(gòu)成,表明包的類型和格式。根據(jù)PID的不同,USB協(xié)議中規(guī)定的包類型有令牌包、數(shù)據(jù)包、握手包和特殊包等。
USB事務(wù)通常有兩個(gè)或三個(gè)包組成:令牌包、數(shù)據(jù)包和握手包,令牌包用來啟動(dòng)一個(gè)事務(wù),總是由主機(jī)發(fā)送;數(shù)據(jù)包用來傳輸數(shù)據(jù);握手包由數(shù)據(jù)接收者進(jìn)行發(fā)送,表明數(shù)據(jù)的接收情況。批量、同步和中斷傳輸每次傳輸都是一個(gè)事務(wù),控制傳輸包括三個(gè)階段:建立過程、數(shù)據(jù)過程和狀態(tài)過程。
針對不同的數(shù)據(jù)傳輸場景,USB分為四種數(shù)據(jù)傳輸模式,這四種傳輸模式分別由不同的包(packet)組成,并且有不同的數(shù)據(jù)處理策略。每種數(shù)據(jù)傳輸模式的流程示意圖以及應(yīng)用場景如下:
- 控制傳輸一般用于命令和狀態(tài)的傳輸,分為控制讀、控制寫和無數(shù)據(jù)控制傳輸。在設(shè)備枚舉的過程中,采用控制傳輸方式進(jìn)行數(shù)據(jù)傳輸。
- 批量傳輸分為批量讀和批量寫,用于數(shù)據(jù)量大、對實(shí)時(shí)性要求不高的場合,如U盤。
- 中斷傳輸用于數(shù)據(jù)量小的場合,保證查詢頻率,如鼠標(biāo)、鍵盤。
- 同步傳輸用于數(shù)據(jù)量大、同時(shí)對實(shí)時(shí)性要求較高的場合,如音視頻。不保證數(shù)據(jù)完整性,沒有ACK/NAK應(yīng)答包,不進(jìn)行數(shù)據(jù)重傳。
13.2.1.4USB描述符
- 一個(gè)USB設(shè)備通常有一個(gè)或多個(gè)配置,但在同一時(shí)刻只能有一個(gè)配置;
- 一個(gè)配置通常有一個(gè)或多個(gè)接口;
- 一個(gè)接口通常有一個(gè)或多個(gè)端點(diǎn);
在USB通信中,USB設(shè)備需要配置多個(gè)USB描述符用以枚舉階段將描述符返回給主機(jī),用以主機(jī)的枚舉以及識(shí)別。USB描述符包括設(shè)備描述符、配置描述符、接口描述符、端點(diǎn)描述符以及字符串描述符等。在GD32 USBD固件庫中,針對各種描述符都按照USB協(xié)議定義了相關(guān)結(jié)構(gòu)體,具體說明如下。
- 設(shè)備描述符
每個(gè)設(shè)備必須有一個(gè)設(shè)備描述符,設(shè)備描述符提供了關(guān)于設(shè)備的配置、設(shè)備所歸屬的類、設(shè)備所遵循的協(xié)議代碼、VID、PID等信息,其相關(guān)結(jié)構(gòu)體定義如下。
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;
- 配置描述符
每個(gè)USB設(shè)備都至少具有一個(gè)配置描述符,在設(shè)備描述符中規(guī)定了該設(shè)備有多少種配置,每種配置都有一個(gè)描述符,其相關(guān)結(jié)構(gòu)體定義如下。
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;
- 接口描述符
接口描述符用以描述接口信息,接口描述符不能單獨(dú)返回,必須附著在配置描述符后一并返回,其相關(guān)結(jié)構(gòu)體定義如下。
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;
- 端點(diǎn)描述符
端點(diǎn)描述符用以描述端點(diǎn)信息,端點(diǎn)描述符不能單獨(dú)返回,必須附著在配置描述符后一并返回,其相關(guān)結(jié)構(gòu)體定義如下。
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 attributes */ 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)品、序列號(hào)、配置和接口的字符串的索引。類和制造商專屬描述符可含有指向額外字符串描述符的索引。對字符串描述符的支持是可選的,有些類可能會(huì)需要它們。
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枚舉實(shí)際上是host檢測到device插入后,通過發(fā)送各種標(biāo)準(zhǔn)請求,請device返回各種USB描述符的過程。USB枚舉的示意圖如下:
13.2.2GD32 USBHS模塊簡介
GD32H7系列MCU提供了最多兩個(gè)USB2.0高速USBHS接口模塊,USBHS包含了一個(gè)內(nèi)部的USB PHY,可以配置成全速或高速,并且不再需要外部PHY芯片。USBHS可以支持USB 2.0協(xié)議所定義的所有四種傳輸方式(控制傳輸、批量傳輸、中斷傳輸和同步傳輸)。
USBHS主要特性如下:
n支持USB 2.0高速(480Mb/s)/全速(12Mb/s)/低速(1.5Mb/s)主機(jī)模式;
n支持USB 2.0高速(480Mb/s)/全速(12Mb/s)設(shè)備模式;
n支持遵循HNP(主機(jī)協(xié)商協(xié)議)和SRP(會(huì)話請求協(xié)議)的OTG協(xié)議;
n支持所有的4種傳輸方式:控制傳輸、批量傳輸、中斷傳輸和同步傳輸;
n支持高帶寬中斷和同步傳輸;
n在主機(jī)模式下,包含USB事務(wù)調(diào)度器,用于有效地處理USB事務(wù)請求;
n包含一個(gè)4KB的FIFO RAM;
n在主機(jī)模式下,支持16個(gè)通道;
n在主機(jī)模式下,包含2個(gè)發(fā)送FIFO(周期性發(fā)送FIFO和非周期性發(fā)送FIFO)和1個(gè)接收
FIFO(由所有的通道共享);
n在設(shè)備模式下,包含8個(gè)發(fā)送FIFO(每個(gè)IN端點(diǎn)一個(gè)發(fā)送FIFO)和1個(gè)接收FIFO(由所有
的OUT端點(diǎn)共享);
n在主機(jī)模式下,若在高速模式下操作,支持PING協(xié)議;
n在設(shè)備模式下,支持8個(gè)OUT端點(diǎn)和8個(gè)IN端點(diǎn);
n在設(shè)備模式下,支持遠(yuǎn)程喚醒功能;
n包含一個(gè)支持USB OTG協(xié)議的USB PHY;
n包含一個(gè)內(nèi)部DMA調(diào)度器和引擎,每個(gè)應(yīng)用請求都可在USBHS和系統(tǒng)之間執(zhí)行數(shù)據(jù)拷貝;
n在主機(jī)模式下,SOF的時(shí)間間隔可動(dòng)態(tài)調(diào)節(jié);
n可將SOF脈沖輸出到PAD;
n可檢測ID引腳電平和VBUS電壓;
n在主機(jī)模式或者OTG A設(shè)備模式下,需要外部部件為連接的USB設(shè)備提供電源;
n支持1.2版電池充電規(guī)范中描述的電池充電檢測(BCD);
n支持2.0版USB OTG補(bǔ)充協(xié)議中描述的附加檢測協(xié)議(ADP);
n支持USB 2.0鏈路層電源管理附錄和USB2.0工程變更通知單勘誤表中描述的鏈路電源管理(LPM)。
USBD模塊框圖如下所示,該系列有兩個(gè)USB HS模塊(USB_HS0和USB_HS1),均支持ULPI接口,允許外部HS收發(fā)器高速傳輸U(kuò)SB的數(shù)據(jù)。
GD32H7 USBHS使用注意事項(xiàng)可通過以下文檔學(xué)習(xí):
https://www.gd32mcu.com/data/documents/applicationNote/AN117_GD32H7xx_USBHSshiyongzhuyishixiang_Rev1.0.pdf
13.2.3USBFS固件庫說明
USBFS 固件庫使用指南可以參考官網(wǎng)相關(guān)文檔,下載地址如下:https://www.gd32mcu.com/data/documents/userManual/AN050_GD32_USBFS_USBHS_Firmware_Library_User_Guide_Rev1.0_CN.pdf
13.3硬件設(shè)計(jì)
GD32H757海棠派開發(fā)板的USB通信接口選擇的是目前較為通用的Type C接口,讀者手中的用于手機(jī)充電的Type C通信線即可使用。
13.4代碼解析
本例程主要實(shí)現(xiàn)通過按鍵向PC發(fā)送鍵值的現(xiàn)象,實(shí)現(xiàn)模擬鍵盤的效果。
本例程主函數(shù)如下所示。
C
int main(void)
{
driver_init(); /* 延時(shí)和公共驅(qū)動(dòng)部分初始化 */
bsp_uart_init(&BOARD_UART); /* 打印串口初始化 */
bsp_led_group_init(); /* 初始化LED組 */
#ifdef USE_ULPI_PHY
usb_gpio_config();
#endif /* USE_ULPI_PHY */
usb_rcu_config();
usb_timer_init();
hid_itfop_register (&hid_keyboard, &fop_handler);
#ifdef USE_USBHS0
#ifdef USE_USB_FS
usb_para_init (&hid_keyboard, USBHS0, USB_SPEED_FULL);
#endif
#ifdef USE_USB_HS
usb_para_init (&hid_keyboard, USBHS0, USB_SPEED_HIGH);
#endif
#endif /* USE_USBHS0 */
#ifdef USE_USBHS1
#ifdef USE_USB_FS
usb_para_init (&hid_keyboard, USBHS1, USB_SPEED_FULL);
#endif
#ifdef USE_USB_HS
usb_para_init (&hid_keyboard, USBHS1, USB_SPEED_HIGH);
#endif
#endif /* USE_USBHS1 */
usbd_init (&hid_keyboard, &hid_desc, &usbd_hid_cb);
#ifdef USE_USB_HS
#ifndef USE_ULPI_PHY
#ifdef USE_USBHS0
pllusb_rcu_config(USBHS0);
#elif defined USE_USBHS1
pllusb_rcu_config(USBHS1);
#else
#endif
#endif /* !USE_ULPI_PHY */
#endif /* USE_USB_HS */
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);
}
}
C
void usb_rcu_config(void)
{
pmu_usb_regulator_enable();
pmu_usb_voltage_detector_enable();
while (pmu_flag_get(PMU_FLAG_USB33RF) != SET) {
}
#ifdef USE_USB_FS
#ifndef USE_IRC48M
#ifdef USE_USBHS0
rcu_usb48m_clock_config(IDX_USBHS0, RCU_USB48MSRC_PLL0R);
#endif /* USE_USBHS0 */
#ifdef USE_USBHS1
rcu_usb48m_clock_config(IDX_USBHS1, RCU_USB48MSRC_PLL0R);
#endif /* USE_USBHS1 */
#else
/* enable IRC48M clock */
rcu_osci_on(RCU_IRC48M);
/* wait till IRC48M is ready */
while (SUCCESS != rcu_osci_stab_wait(RCU_IRC48M)) {
}
#ifdef USE_USBHS0
rcu_usb48m_clock_config(IDX_USBHS0, RCU_USB48MSRC_IRC48M);
#endif /* USE_USBHS0 */
#ifdef USE_USBHS1
rcu_usb48m_clock_config(IDX_USBHS1, RCU_USB48MSRC_IRC48M);
#endif /* USE_USBHS1 */
#endif /* USE_IRC48M */
#endif /* USE_USB_FS */
#ifdef USE_USBHS0
rcu_periph_clock_enable(RCU_USBHS0);
#endif /* USE_USBHS0 */
#ifdef USE_USBHS1
rcu_periph_clock_enable(RCU_USBHS1);
#endif /* USE_USBHS1 */
#ifdef USE_ULPI_PHY
#ifdef USE_USBHS0
rcu_periph_clock_enable(RCU_USBHS0ULPI);
#endif
#ifdef USE_USBHS1
rcu_periph_clock_enable(RCU_USBHS1ULPI);
#endif
#endif /* USE_ULPI_PHY */
}
rcu的配置如下,主要用于配置USB時(shí)鐘,USB需要一個(gè)穩(wěn)定的48M時(shí)鐘,歷程中根據(jù)相關(guān)宏定義開關(guān)進(jìn)行選擇配置。
C void usb_rcu_config(void) { pmu_usb_regulator_enable(); pmu_usb_voltage_detector_enable(); while (pmu_flag_get(PMU_FLAG_USB33RF) != SET) { } #ifdef USE_USB_FS #ifndef USE_IRC48M #ifdef USE_USBHS0 rcu_usb48m_clock_config(IDX_USBHS0, RCU_USB48MSRC_PLL0R); #endif /* USE_USBHS0 */ #ifdef USE_USBHS1 rcu_usb48m_clock_config(IDX_USBHS1, RCU_USB48MSRC_PLL0R); #endif /* USE_USBHS1 */ #else /* enable IRC48M clock */ rcu_osci_on(RCU_IRC48M); /* wait till IRC48M is ready */ while (SUCCESS != rcu_osci_stab_wait(RCU_IRC48M)) { } #ifdef USE_USBHS0 rcu_usb48m_clock_config(IDX_USBHS0, RCU_USB48MSRC_IRC48M); #endif /* USE_USBHS0 */ #ifdef USE_USBHS1 rcu_usb48m_clock_config(IDX_USBHS1, RCU_USB48MSRC_IRC48M); #endif /* USE_USBHS1 */ #endif /* USE_IRC48M */ #endif /* USE_USB_FS */ #ifdef USE_USBHS0 rcu_periph_clock_enable(RCU_USBHS0); #endif /* USE_USBHS0 */ #ifdef USE_USBHS1 rcu_periph_clock_enable(RCU_USBHS1); #endif /* USE_USBHS1 */ #ifdef USE_ULPI_PHY #ifdef USE_USBHS0 rcu_periph_clock_enable(RCU_USBHS0ULPI); #endif #ifdef USE_USBHS1 rcu_periph_clock_enable(RCU_USBHS1ULPI); #endif #endif /* USE_ULPI_PHY */ } |
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 TIM2 global interrupt */ nvic_irq_enable((uint8_t)TIMER2_IRQn, 1U, 0U); rcu_periph_clock_enable(RCU_TIMER2); } |
注冊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->dev.user_data = (void *)hid_fop; return USBD_OK; } return USBD_FAIL; } |
USBD內(nèi)核初始化函數(shù)如下所示。在該代碼清單中,首先配置設(shè)備類callback函數(shù),之后創(chuàng)建字符串,配置USB以及初始化USB內(nèi)核,斷開USB連接,初始化USB設(shè)備模式,之后設(shè)置USB連接,將USB連接狀態(tài)配置為DEFAULT默認(rèn)狀態(tài),啟動(dòng)狀態(tài)機(jī)。
C void usbd_init(usb_core_driver *udev, 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); /* initializes the USB core*/ (void)usb_core_init(udev->bp, &udev->regs); /* set device disconnect */ usbd_disconnect(udev); /* initializes device mode */ (void)usb_devcore_init(udev); /* set device connect */ usbd_connect(udev); udev->dev.cur_status = (uint8_t)USBD_DEFAULT; } |
配置USB中斷函數(shù)如下。
C void usb_intr_config(void) { nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); #ifdef USE_USBHS0 nvic_irq_enable((uint8_t)USBHS0_IRQn, 3U, 0U); #endif /* USE_USBHS0 */ #ifdef USE_USBHS1 nvic_irq_enable((uint8_t)USBHS1_IRQn, 3U, 0U); #endif /* USE_USBHS0 */ /* enable the power module clock */ rcu_periph_clock_enable(RCU_PMU); #ifdef USE_USBHS0 /* USB wakeup EXTI line configuration */ exti_interrupt_flag_clear(EXTI_31); exti_init(EXTI_31, EXTI_INTERRUPT, EXTI_TRIG_RISING); exti_interrupt_enable(EXTI_31); nvic_irq_enable((uint8_t)USBHS0_WKUP_IRQn, 1U, 0U); #endif /* USE_USBHS0 */ #ifdef USE_USBHS1 /* USB wakeup EXTI line configuration */ exti_interrupt_flag_clear(EXTI_32); exti_init(EXTI_32, EXTI_INTERRUPT, EXTI_TRIG_RISING); exti_interrupt_enable(EXTI_32); nvic_irq_enable((uint8_t)USBHS1_WKUP_IRQn, 1U, 0U); #endif /* USE_USBHS1 */ #ifdef USB_DEDICATED_EP1_ENABLED #ifdef USE_USBHS0 nvic_irq_enable((uint8_t)USBHS0_EP1_OUT_IRQn, 1U, 0U); nvic_irq_enable((uint8_t)USBHS0_EP1_IN_IRQn, 1U, 0U); #endif /* USE_USBHS0 */ #ifdef USE_USBHS1 nvic_irq_enable((uint8_t)USBHS1_EP1_OUT_IRQn, 1U, 0U); nvic_irq_enable((uint8_t)USBHS1_EP1_IN_IRQn, 1U, 0U); #endif /* USE_USBHS1 */ #endif /* USB_DEDICATED_EP1_ENABLED */ } |
內(nèi)部上拉電阻被上拉后,主機(jī)將會(huì)對設(shè)備進(jìn)行枚舉,設(shè)備端采用while (USBD_CONFIGURED != hid_keyboard.dev.cur_status) 語句進(jìn)行等待。當(dāng)USB設(shè)備狀態(tài)變?yōu)閁SBD_CONFIGURED狀態(tài)時(shí),表明設(shè)備枚舉完成。
枚舉完成之后,程序?qū)⑦M(jìn)入主循環(huán)中,在主循環(huán)中,循環(huán)調(diào)用HID USB模擬鍵盤數(shù)據(jù)處理函數(shù),在該函數(shù)中,首先判斷上次傳輸是否完成,完成之后通過掃描按鍵的方式查看按鍵是否被按下,若按鍵被按下,則通過hid_report_send()函數(shù)發(fā)送鍵盤報(bào)告數(shù)據(jù)。
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: printf_log("Press A on the keyboard\r\n"); bsp_led_toggle(&LED1); hid->data[2] = 0x04U; break; case CHAR_B: printf_log("Press B on the keyboard\r\n"); bsp_led_toggle(&LED2); hid->data[2] = 0x05U; break; default: break; } if (0U != hid->data[2]) { hid_report_send(udev, hid->data, HID_IN_PACKET); } } } |
報(bào)文發(fā)送函數(shù)定義如下,該函數(shù)包含三個(gè)參數(shù),udev為初始化后的設(shè)備操作結(jié)構(gòu)體;report為發(fā)送報(bào)告緩沖區(qū)地址;len為發(fā)送報(bào)告的長度。在該函數(shù)中,如果設(shè)備已經(jīng)被枚舉成功,則首先將prev_transfer_complete標(biāo)志位設(shè)置為0,表明接下來將進(jìn)行發(fā)送數(shù)據(jù),數(shù)據(jù)并未發(fā)送完成,之后,調(diào)用usbd_ep_send()將需要發(fā)送的報(bào)告拷貝到USB外設(shè)緩沖區(qū)中并設(shè)置端點(diǎn)為有效狀態(tài),等待主機(jī)發(fā)送IN令牌包,USB設(shè)備將外設(shè)緩沖區(qū)中的數(shù)據(jù)發(fā)送給主機(jī)。
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; } |
當(dāng)數(shù)據(jù)發(fā)送完成,USB設(shè)備將調(diào)用hid_data_in()函數(shù)進(jìn)行數(shù)據(jù)處理。該函數(shù)程序如下所示。在該函數(shù)中,首先判斷hid->data[2]的數(shù)據(jù)是否為0x00,如果不為0x00表明上次發(fā)送的為按鍵按下的鍵值,還需發(fā)送按鍵松開的鍵值,如果為0x00表明上次按鍵按下和松開的鍵值均已發(fā)送完成,之后將prev_transfer_complete設(shè)置為1,表明上一次的按鍵數(shù)據(jù)傳輸完成,可進(jìn)行下次按鍵數(shù)據(jù)傳輸。
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數(shù)據(jù)流程標(biāo)志位進(jìn)行數(shù)據(jù)發(fā)送控制,讀者可使用該標(biāo)志位用于對數(shù)據(jù)發(fā)送的控制,當(dāng)該標(biāo)志位為0的時(shí)候,表明數(shù)據(jù)已被填送到USB緩沖區(qū),但還沒有發(fā)送給主機(jī),此時(shí)MCU不能繼續(xù)調(diào)用發(fā)送函數(shù)向緩沖區(qū)中填數(shù)據(jù),否則可能導(dǎo)致數(shù)據(jù)覆蓋丟失,正確做法是等待該標(biāo)志位置位,表明上一包數(shù)據(jù)已被主機(jī)讀取,然后再繼續(xù)發(fā)送后續(xù)數(shù)據(jù)。 |
13.5實(shí)驗(yàn)結(jié)果
將本例程燒錄到海棠派開發(fā)板中,通過Type C數(shù)據(jù)線連接開發(fā)板和PC,之后分別按下WKUP和USER按鍵,將會(huì)向PC發(fā)送A、B鍵值。
本教程由GD32 MCU方案商聚沃科技原創(chuàng)發(fā)布,了解更多GD32 MCU教程,關(guān)注聚沃科技官網(wǎng)
-
單片機(jī)
+關(guān)注
關(guān)注
6037文章
44558瀏覽量
635298 -
usb
+關(guān)注
關(guān)注
60文章
7945瀏覽量
264657 -
開發(fā)板
+關(guān)注
關(guān)注
25文章
5050瀏覽量
97471 -
虛擬鍵盤
+關(guān)注
關(guān)注
0文章
11瀏覽量
7836 -
GD32
+關(guān)注
關(guān)注
7文章
403瀏覽量
24351
發(fā)布評(píng)論請先 登錄
相關(guān)推薦
評(píng)論