一、USB_OTG简介
( E6 Y9 w. V' V$ O- {( O3 ? USB_OTG(OTG,ON THE GO)是一款双角色设备(DRD) 控制器,同时支持从机(USB DEVICE)功能和主机(USB HOST)功能。在主机模式下,OTG 支持全速(OTG_FS,12 Mb/s)和低速(OTG_LS,1.5 Mb/s)收发器,而从机模式下则仅支持全速(FS,12 Mb/s)收发器。主机模式下需要的唯一外部设备是提供VBUS的电荷泵。在驱动实现层面,USB OTG是USB Device和USB Host 的基础。在实际使用,USB OTG是USB Device和USB Host 的底层驱动。, T4 j& U+ P: ]" l6 D) E
: }" `9 |+ M# P7 `# @5 x1 A stm32芯片的通常USB物理接口管理两个引脚:4 D7 s/ r- w8 e. _% b
5 k$ J+ m8 q7 P9 x+ P) a
DP/DM:内置上下拉,由控制器来设置不同类型的需求8 l# T: T% A) B( t2 T1 i ^, b: i7 b
* }: n% o, S8 |8 k. n
OTG实现时,额外配置ID引脚:
$ U1 i' m# l' D }1 k, ]4 n- { ID:检测插入的线是B端还是A端,用于区分A类和B类设备,
6 L$ |1 [4 ?9 Z/ J/ A) M% ~- R: o! }6 J' E7 z+ ~2 q3 k
USB Host 模式下,需加配置Vbus引脚:9 ~2 W! P: I/ W
Vbus:内置检测器,用于测试UBUS的有效性
Q; V" a# ~4 j# z7 N4 T0 `/ X% q% I& i) j- k7 ?
额外地需要支持SOF和NOF时,需要加配置SOF和NOF引脚。6 f1 @# [) |: o) s! g$ l
& [. `: I; `; ]: ^; \4 F 例如本文采用的STM32L496VGT3芯片,其配置支持到的引脚如下:
, Y; d7 \+ A. \1 @8 t5 x4 ~/ ?8 E5 ]4 x
8 s- n3 N3 Y3 j/ Y- {6 t0 D* p
# r! K9 O, ]8 r7 w0 {, C. DUSB协议栈的层次划分0 x5 R3 S* s" L( `' [' ^
一个Host可能有一个或者多个Device
* Y5 ]# j5 v# n8 g" \& y& \3 z 一个Device可能有一个或者多个Interface
8 Z' ^! ~. D6 s1 A r- V# F4 N 一个Interface可能有一个或者多个Endpoint" }* V/ p2 E* M8 @
Host(主机)连的是Device(设备),这一层是走物理连接的,也就是信号线实际连接两台设备。) ?6 I3 Y8 @; l0 g; D. L2 c: s
Device(设备)下可能有多个Interfece(接口),从这开始是逻辑概念,一个Interface,就是一个独立的功能接口,每个Interface模拟一个设备功能,比如集成了键盘和鼠标的USB设备,里面就是两个interface,一个是键盘,另一个是鼠标。Interface之间通常是隔离的,互相不干扰。
o3 q' w4 V9 M5 Y# m! Z 每个Interface(接口)下面有一个或者多个Endpoint(端点),这也是逻辑概念,例如控制信号端口、数据信号端口等。端点是USB设备通信的基本单位,所有通信几乎都是从端点发起的。
! e$ a, b, a3 Q" G' p0 ]$ ?& X6 B# f# _; ]/ Z* T; ?
* I, Z# U1 b1 C& A' N6 v
二、创建工程及USB_Device配置( l7 |0 u' K" m3 w5 Q! P- d
本博文基于STM32L496VGTX3芯片实现
% E: x; S! m5 _- v C9 E# }4 ~7 O
基于该工程的(.ioc)文件创建了新工程stm32L496VGTx_USB,并移植了相关源码,实现了lpusart通信以及lcd显示字体。; K: `- e: a3 b& d, D& h9 q: {2 q
- [: c R8 k8 C7 w5 p6 D/ o4 t9 v 现打开工程的(.ioc)配置文件,进入cubeMX配置界面,开启USB_OTG_FS的USB Device功能,参数保持默认配置。
* a) b j6 g/ ~: D( P
4 i% k; m/ {( l/ I( B7 o- e1 E. j
. u3 z" n: r1 b; p. A9 G, a) t S; C
. Q/ t/ r+ o# q; ^
确保USB_OTG_FS的中断功能已经开启。
2 H p7 N3 q' `# ^1 _. ^4 u: @ S* F2 e4 U5 b9 W) N& ?
6 `+ t% J* h8 T( Q- }+ p3 S' T: } W/ b% h1 u, Z( V
开启USB_OTG_FS的USB Device选项后,Middleware栏目可以去配置USB_Device信息,本文MCU作为USB_Device与笔记本电脑USB_HOST相连接,实现串口通信收发数据,因此选择通信类型虚拟串口功能,如下图所示,参数保持默认。
: m. ?' B7 ~5 Q* l6 a! C4 d; t. d% x! N
' F W" t8 a( Z9 l
7 X) W! h9 g, u1 _9 t5 n4 C9 M
设备描述符按默认设置:
4 k( c& r& e3 a4 F- N
9 x, m! G8 I/ b) O' I
/ _ A/ G8 O5 V% l
" J: b( k& A" N; J
由于USB驱动引入了中间件代码,并比较复杂,需要更多缓存支持,现进入工程配置页面,调整min heap size和min stack size为合适的值,防止因数值较少而出现无法编译、编译异常、运行错误等情况出现。) O e M3 W5 A0 ~, T& O
$ `1 F4 v$ e6 |) U( e* n
) X$ \$ {3 b' h) R
3 O* }1 j0 T0 k* H 点击保存输出生成代码,关于USB_Device驱动相关代码如下,其中usb_cdc_if.h/c源码是用户可修改源文件:4 N; R/ N5 @) q/ R j# l! T
! |0 w% [/ |5 O/ a
6 i$ @8 k! c3 S
0 f, L, K/ |& K0 c9 E5 [% x
三、USB_Device驱动实现设计
7 Z5 {' ]# v8 B9 f& @2 R( B 【1】在usb_cdc_if.h中,添加USB相关全局变量(接收缓存数组、最大长度、接收标记及长度)
6 S8 B7 q$ [) s7 [/ ^- /* USER CODE BEGIN INCLUDE */
( J" `, U# H+ p9 S8 A3 g* \# ?: N B - #define USB_REC_LEN 256//定义USB串口最大接收字节数
4 @% E4 }5 G" M+ d; g7 z0 I - extern uint8_t USB_RX_BUF[USB_REC_LEN];//接收缓冲,最大USB_REC_LEN个字节.末字节为换行符( d) O! h3 g" n
- extern uint16_t USB_RX_STA;//接收状态标记(接收到的有效字节数量) h. f2 w E0 M- V# I0 N
- /* USER CODE END INCLUDE */
复制代码
+ N6 x. _' l' l3 l1 Y 【2】在usb_cdc_if.h中,添加USB打印输出函数USB_printf声明
, A% d8 U m- q+ S2 @& v1 l$ X4 c- /* USER CODE BEGIN EXPORTED_VARIABLES */* I& x# r* I ]: J; X/ c$ C
- void USB_printf(const char *format, ...);//USB模拟串口的打印函数
$ k' A1 }$ d) U! l - /* USER CODE END EXPORTED_VARIABLES */
复制代码 % |: z; A+ B* I7 U2 q/ X Y- s+ ~$ b
2 @: u$ u# Z: ~( @) B; L0 r 【3】在usb_cdc_if.c中,调整USB接收函数
! Y/ t$ ]# v0 {5 i- i- static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
! q y+ t3 q# U# g: @# Z - {
1 W. {) v, U$ X - /* USER CODE BEGIN 6 */
7 ]- |* H: ]7 v# H6 A, x - //新增代码开始处3 O: ~& l& b1 [( |; M/ e' x* h* k
- if(*Len<USB_REC_LEN)//判断收到数据量是否小于寄存器上限6 z7 t& v3 n% u. |# D& x6 r1 e
- {6 H+ ^( Y- j" k0 z; ^5 E
- uint16_t i; x8 {. @) s9 E
- USB_RX_STA = *Len;//将数据量值放入标志位
4 w( E* g2 X3 y* I% Q7 T) ` - for(i=0;i<*Len;i++)//循环(循环次数=数据数量)
7 c+ |: x4 J$ N( U - USB_RX_BUF[i] = Buf[i];//将数据内容放入数据寄存器
8 I j% M0 s3 m1 |; { - }( Z$ P+ G1 [& o3 l2 T# H
- //新增代码结束处, b6 s4 ]1 ?6 C o
- USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);) ~' z6 u, N9 ~# z# B
- USBD_CDC_ReceivePacket(&hUsbDeviceFS);9 g; t) @3 q- p8 b% x: A
- return (USBD_OK);
5 ~8 {6 |) X) V6 X) F3 g - /* USER CODE END 6 */; `$ S- E& C1 ?# u- X
- }
复制代码 5 g1 g5 V k5 _& t5 r) z9 d: P
【4】在usb_cdc_if.c中,调整USB发送函数
3 \" c- z8 I( r9 A' } m! N- uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
( z5 G, t+ @5 G' T6 c9 R - {* I( D$ V- A1 A$ o( o0 K# o
- uint8_t result = USBD_OK;
% i+ c3 @2 m; H- ?5 { - /* USER CODE BEGIN 7 */
' ]8 l3 D# D2 n) P- ^' \9 S/ l - //注释旧代码1 V; t5 _) k& i5 z
- // USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;
% l/ w% R9 n* a - // if (hcdc->TxState != 0){7 j* O! q! H4 `6 h6 Z
- // return USBD_BUSY;
- C0 {) O- `/ T: c. M - // }, ~- E6 B9 F8 x9 \1 p/ ]4 n+ S% ]
- // USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
+ l( X1 |- x+ b - // result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);
: m: _: g" G& ]% D% x+ {; ? - //新增新代码
& E, p2 P( A* s, |$ ]4 y2 y - uint32_t TimeStart = HAL_GetTick();
8 i- c: l+ a. z - USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;/ B& f3 s. {5 r" k% {1 c
- while(hcdc->TxState): X0 C" c4 K1 j) [
- {2 A @' T' v7 b
- if(HAL_GetTick()-TimeStart > 10)
6 a# L3 @1 ]5 g3 F$ H- U+ | - return USBD_BUSY;5 c; S, c. N# q/ l% r
- else
8 S6 R8 O; r3 K c4 X* ` - break;
/ |. \6 X3 |4 Y& V0 r - }
- O6 H" F+ P; w3 s - USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
. Q; n4 ~7 w1 @5 d8 Y4 @4 K - result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);
4 }" c; D5 y- V) `# z* E - TimeStart = HAL_GetTick();/ R6 W- F. z9 r# M1 a
- while(hcdc->TxState)
) ] e/ N7 p" I" X! r7 F - {
% S Z/ L" r# c - if(HAL_GetTick()-TimeStart > 10)4 J) o! `0 `9 S, p1 l7 d0 |# u
- return USBD_BUSY;
5 K1 m4 U8 m3 s4 a: n/ s - }+ c: J0 E$ d4 w7 j& ~# ]
- /* USER CODE END 7 */% Q& V- w! k/ V, C4 U* o+ R
- return result;
3 }. @$ t* |& U# p) T/ I. W - }
复制代码 + k! n1 q2 G6 g& }4 ?" _# x
【5】在usb_cdc_if.c中,实现USB打印输出函数USB_printf定义
2 J4 c y$ B$ i% S5 h0 u$ [- /* USER CODE BEGIN PRIVATE_FUNCTIONS_IMPLEMENTATION */; c% z8 n# V; Q1 \8 e+ k( U
- #include <stdarg.h>* g6 Y/ d" v: z: D
- void USB_printf(const char *format, ...)//USB模拟串口的打印函数
* `9 \4 J% Q% P2 g* ?! D - {% p/ `& M+ B" I. r( \( A
- va_list args;2 U! p$ \ y# @/ \5 p$ U5 `% E0 b+ a; k. m
- uint32_t length;! o. q0 g D) [+ i, E
- va_start(args, format);1 n. n3 K! d9 H# x
- length = vsnprintf((char *)UserTxBufferFS, APP_TX_DATA_SIZE, (char *)format, args);
' @7 C! e' e% k3 F! s$ ^/ P) R - va_end(args);
) E* K8 X, O9 j: I+ } - CDC_Transmit_FS(UserTxBufferFS, length);
# v7 _' A7 Q, f7 N - }
9 P: @0 g/ R. P/ y5 e - /* USER CODE END PRIVATE_FUNCTIONS_IMPLEMENTATION */
复制代码
$ R! d1 l8 c+ g+ h1 t. [1 Q. d" d 【6】在main.c文件中,添加USB驱动头文件支持
& A( D! C9 S& Q! ^8 \) {- /* USER CODE BEGIN Includes */
z d2 a- S6 z3 b" u - #include "../../ICore/led/led.h"
, q7 g& k1 {: e, J - #include "../../ICore/oled/oled.h"
! d s9 s% ~# {0 b+ Y+ X% B - #include "../../USB_DEVICE/App/usbd_cdc_if.h"* I! y i* Q' I% w# x5 Z
- /* USER CODE END Includes */
复制代码
9 a, r# b6 {7 u/ { 【7】在main主函数初始化处5 E2 q/ v5 q6 T; l4 ^# ~0 O
- /* USER CODE BEGIN 2 */
" k2 e% u3 c6 p2 d - OLED_init();
. H3 Q/ ?2 C& J. k9 } - //设置OLED蓝色背景显示. y# F# T! {1 N5 c
- BSP_LCD_Clear_DMA(LCD_DISP_BLUE);
; X1 Y4 c5 L# M% ?3 _- | - printf("OLED_Clear_DMA\r\n");/ y$ X6 l) U' i+ F0 K
- /* USER CODE END 2 */
复制代码
- @1 v. G8 f3 R3 s8 z! B3 K5 ^% H【8】在main主函数循环体内实现1 N2 w6 P# u/ y4 U g
- /* USER CODE BEGIN WHILE */4 x& m5 i4 l" q# { c& v) B
- while (1) ?& A; q8 I& w- G" j; Z
- {
1 h+ Z% c/ K) q. ]8 ~5 k) r - //USB模拟串口的查寻接收处理(其编程原理与USART1串口收发相同)
0 a k( i9 l4 d( [! f3 I: a$ S5 J+ o+ b - if(USB_RX_STA!=0)//判断是否有数据
' A7 w8 l+ _3 I3 L$ X - {6 L7 `5 q# p; k: E% g1 Y- Q ]
- OLED_printf(10,42,"%.*s",USB_RX_STA, USB_RX_BUF);
1 v1 `( H- _0 H - Toggle_led1();
( k6 y# B7 C# Z: N% u3 }+ x4 h - USB_RX_STA=0;//数据标志位清0
- F- D3 D/ S6 H4 @ - memset(USB_RX_BUF,0,sizeof(USB_RX_BUF));//USB串口数据寄存器清0 g7 ?' m% R0 b; t# G1 ]4 U. [
- HAL_Delay(10);//等待/ a7 D; H# B5 e# ` j
- }
% O# v. Y* P5 u1 B* S - /* USER CODE END WHILE */
复制代码
; f& F0 g+ _8 \! @' r# b8 I四、编译及下载* l. W* j$ ]* X& [" k5 F% Y
编译下载,该开发板原理框图,需要将跳线帽重新插拔设置,相当于原理的USB-ST-LINK连接到USB_MCU上,即原理lpusart通信及ST-LINK下载的USB接口改为连接到USB_MCU的DM/DP引脚上实现通信。
& C7 t( l7 ]; k [ K5 g/ t. h- T4 X- h: E8 q6 F- w2 a
! D7 j3 Q* i X2 u7 R6 ]2 p
4 _2 p! E6 h, J" `) ]/ z a/ }4 p 更改调线帽后,打开串口工具,发送数据,顺利在点(10,42)位置绘制输入文字,LED1等会切换状态。+ i: p4 o6 A) {" K, I7 f ]7 e
2 q! F/ B9 X3 I
3 J5 ^7 ]) b: L O6 m
5 T8 {, M1 U; P6 w+ ]; v————————————————
, H- T6 m5 k, _' \& x版权声明:py_free-物联智能
/ _! W5 D5 V1 B' x3 k2 b! K4 V2 ~! e如有侵权请联系删除
. k/ o6 c d$ L O; L( d4 E! ^3 K; j+ f! }5 E
6 \6 @0 ^& ^: G; v7 d |