一、USB_OTG简介
: A8 r/ y- }' k7 F- ^6 L 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 的底层驱动。' X" p' K% o" a
" O: `+ x) g8 m! V6 }4 D stm32芯片的通常USB物理接口管理两个引脚:
1 m; X- T+ I9 R3 r
( Q" V6 N5 c2 @% | DP/DM:内置上下拉,由控制器来设置不同类型的需求+ I! ^0 P1 w# A4 C
( x2 t8 J. p) L6 H2 S2 G* V
OTG实现时,额外配置ID引脚:
" u. Z# S7 d. S6 F5 W. r ID:检测插入的线是B端还是A端,用于区分A类和B类设备,
. Y. w' w$ q% f! L- y7 c
$ h8 y# m7 m- @) w USB Host 模式下,需加配置Vbus引脚:! @* _8 V9 Y: N9 V0 f6 x8 w$ |
Vbus:内置检测器,用于测试UBUS的有效性4 _+ e; u9 I3 c4 E1 r
& Y2 v8 t% p5 E" L2 x) }& T 额外地需要支持SOF和NOF时,需要加配置SOF和NOF引脚。
3 L9 F/ S! A: ^) p* t
9 n! c. ^0 l- u& E4 d( y 例如本文采用的STM32L496VGT3芯片,其配置支持到的引脚如下:
2 M+ v$ | ~ @8 g, s
. e7 |2 S7 u; ]( o
, E$ z; n! n1 V! e3 y, c/ |
& y& a" Q n- a$ UUSB协议栈的层次划分
% c( g* g; o: z" u8 I+ y 一个Host可能有一个或者多个Device Q/ @, X# P" h. \7 {3 D
一个Device可能有一个或者多个Interface' U& E# p& y/ W+ F! u
一个Interface可能有一个或者多个Endpoint# p. q( J: c! x/ X# {* T
Host(主机)连的是Device(设备),这一层是走物理连接的,也就是信号线实际连接两台设备。
' A) Z; z- h9 i5 A0 J) ?+ X' C: w6 ` Device(设备)下可能有多个Interfece(接口),从这开始是逻辑概念,一个Interface,就是一个独立的功能接口,每个Interface模拟一个设备功能,比如集成了键盘和鼠标的USB设备,里面就是两个interface,一个是键盘,另一个是鼠标。Interface之间通常是隔离的,互相不干扰。5 v( R9 t: a2 E. \
每个Interface(接口)下面有一个或者多个Endpoint(端点),这也是逻辑概念,例如控制信号端口、数据信号端口等。端点是USB设备通信的基本单位,所有通信几乎都是从端点发起的。, V9 ?2 G) h: a9 }3 A
) A7 s6 n! o% f/ e7 J" J2 z1 C
2 ^$ t# v' L- B2 I二、创建工程及USB_Device配置
9 i0 u$ T/ S2 K/ s 本博文基于STM32L496VGTX3芯片实现
2 M2 T: z) ?6 l3 {
( Z+ \% n5 A8 y$ ], _) Y 基于该工程的(.ioc)文件创建了新工程stm32L496VGTx_USB,并移植了相关源码,实现了lpusart通信以及lcd显示字体。3 R! z. A3 h$ {( E
/ h& J z/ t" J) h, ^) K
现打开工程的(.ioc)配置文件,进入cubeMX配置界面,开启USB_OTG_FS的USB Device功能,参数保持默认配置。
O9 D2 T6 q p; a; F4 I' }" O% G4 G
) b- v6 x2 N1 e; h, k- X. o, V+ X
4 Z1 ^& s4 T' ~% _ 确保USB_OTG_FS的中断功能已经开启。
) Q4 u% P; G% U1 Z- Z r
" q) |/ e% r% ^; E( U
8 \, z3 j3 l, i; l. T
* q) r- T5 x! e
开启USB_OTG_FS的USB Device选项后,Middleware栏目可以去配置USB_Device信息,本文MCU作为USB_Device与笔记本电脑USB_HOST相连接,实现串口通信收发数据,因此选择通信类型虚拟串口功能,如下图所示,参数保持默认。
7 M; b% p+ D, P9 T! a8 p0 F% {. f! K- p7 C* Z/ I
5 Q) \ n4 f) ]* i. Z: I* \
. z4 a4 b, ?: Y' \# A+ e+ P 设备描述符按默认设置:: o" c/ [! w% U1 x% X1 p! I
3 w* D0 g$ e! k5 h1 l' w( ^" ?* a
0 X, _& _/ ?$ D; S! n* T- A- t/ M) I- k0 W- {
由于USB驱动引入了中间件代码,并比较复杂,需要更多缓存支持,现进入工程配置页面,调整min heap size和min stack size为合适的值,防止因数值较少而出现无法编译、编译异常、运行错误等情况出现。
7 d: ^4 m- _" d8 \, G/ z. r3 `; |# ~6 m/ F
5 d0 i: }8 c5 Q, h/ ?
! o( h( X0 ~: B# R& O; L: D/ h9 _
点击保存输出生成代码,关于USB_Device驱动相关代码如下,其中usb_cdc_if.h/c源码是用户可修改源文件:
5 V! [1 o, l/ g m4 B3 p
" ~2 S) V {- G) b* O# G. L; b
, y2 Y. V; a% Z3 `8 D# a I, f6 A
" {( I' u r- U 三、USB_Device驱动实现设计/ ~0 v+ |1 c: P. u. s* e
【1】在usb_cdc_if.h中,添加USB相关全局变量(接收缓存数组、最大长度、接收标记及长度); K: s& y7 ^+ `8 |% u
- /* USER CODE BEGIN INCLUDE */
, L, t5 o& k- |, _. g - #define USB_REC_LEN 256//定义USB串口最大接收字节数
- p$ j' ~# k( ]. r/ B* t - extern uint8_t USB_RX_BUF[USB_REC_LEN];//接收缓冲,最大USB_REC_LEN个字节.末字节为换行符
" {5 e* ?: L& g% V0 C/ x - extern uint16_t USB_RX_STA;//接收状态标记(接收到的有效字节数量)
: Y+ I$ {, Y- v - /* USER CODE END INCLUDE */
复制代码
$ L+ c- d r! E8 g 【2】在usb_cdc_if.h中,添加USB打印输出函数USB_printf声明 x5 T1 Y8 r1 F ~0 c3 l. z
- /* USER CODE BEGIN EXPORTED_VARIABLES */
% H6 K/ s! L- K! \2 v - void USB_printf(const char *format, ...);//USB模拟串口的打印函数
. A* P4 g' L6 K. f4 Q6 S' g* g0 X - /* USER CODE END EXPORTED_VARIABLES */
复制代码 U' D8 N0 F+ c \ Y. X
0 V1 N1 ]/ c! L" Z2 Y& l. a
【3】在usb_cdc_if.c中,调整USB接收函数& L1 t0 ^% T- ], o- i
- static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
+ g9 ^0 K* C# g; X - {4 R3 D% Z; @. M/ ~
- /* USER CODE BEGIN 6 */
* @3 s0 ]% _- ~5 t5 D' E5 k - //新增代码开始处
4 c0 c! ~: G: d - if(*Len<USB_REC_LEN)//判断收到数据量是否小于寄存器上限 V* g! L5 d/ F. h( ^
- {
5 [; Z1 L4 e6 c6 b' | - uint16_t i;/ v7 ], ^. @1 o
- USB_RX_STA = *Len;//将数据量值放入标志位! |: s* E6 i8 `) I* ?3 X! m
- for(i=0;i<*Len;i++)//循环(循环次数=数据数量)4 m( w' v1 c& F" A% B
- USB_RX_BUF[i] = Buf[i];//将数据内容放入数据寄存器9 G# L. a4 `8 f4 W) `
- }% g# S3 Z/ D R c1 z/ t- {1 o! ~
- //新增代码结束处
7 R- {# |1 V, E- a" P) }; P, T - USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
# L+ j; G' b0 ]. | - USBD_CDC_ReceivePacket(&hUsbDeviceFS);
) B* p$ [; G. K9 f& ` - return (USBD_OK);3 }7 k( G1 a' h* N
- /* USER CODE END 6 */" m- Y& L/ d ?; e. A
- }
复制代码
) G( ]+ E0 b# g* M; W: ?& } 【4】在usb_cdc_if.c中,调整USB发送函数
4 R- K5 @4 q; M) ?- uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)- a2 R5 I7 S. G8 U: G U3 s
- {- i# S/ r; v! ^- P6 _
- uint8_t result = USBD_OK;
- L) v3 q5 P5 \2 j+ { - /* USER CODE BEGIN 7 */
* _) ^9 d2 f! [0 @ - //注释旧代码
w6 L0 t8 f; M# X1 V3 Y$ M/ | - // USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;7 q5 |& b- h' |- P
- // if (hcdc->TxState != 0){/ W! [' c( y4 J" R$ e7 p
- // return USBD_BUSY;
$ P G4 K" g2 D - // }8 y) A7 m: F& }/ q- J t$ ^$ V
- // USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);2 ~4 ~1 V9 q0 X( q* y: A9 s5 N3 F: B
- // result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);' u0 o# M1 _* {- k8 Z
- //新增新代码. q( M/ |6 c! j- e' T
- uint32_t TimeStart = HAL_GetTick();
! n) m) Q) h. ~) y3 }; J" |: \8 O9 \ - USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;2 I" t u1 \& _' n7 T: r! n" _
- while(hcdc->TxState)- b, F" G/ j b. t& o
- {
1 X2 |1 N: X# s' k% x" O1 H0 v6 Z - if(HAL_GetTick()-TimeStart > 10)
9 F3 T' Q1 l9 f, J - return USBD_BUSY;
4 j! z3 p6 A7 o - else
1 S, _; i- k1 k" [ - break;
) b( Y6 z5 w' i5 d" _" R- l - }/ x; Y, k. i: Y9 [9 Z: g+ ~( V5 F9 K1 v
- USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
7 ?2 z. C& \7 [ - result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);! `2 P9 P }: S1 |+ c# a
- TimeStart = HAL_GetTick();
0 T$ Z8 q0 l' @7 z* u6 _+ D - while(hcdc->TxState)9 L2 c* u7 j( T% o- F
- {4 c5 W3 K" R: g% r" n; ]
- if(HAL_GetTick()-TimeStart > 10)1 A0 `6 H* s* I% T0 a
- return USBD_BUSY;- a! ^ o0 Q8 j9 i2 o* S9 S
- }
: `5 w! i3 v) p3 i( e" P - /* USER CODE END 7 */$ L6 H2 Z4 ?1 T+ {
- return result;
8 p9 w8 w& F- [6 e# C" o4 D# g - }
复制代码 : P$ S" A- W- t6 M: t3 H% \
【5】在usb_cdc_if.c中,实现USB打印输出函数USB_printf定义$ o* L: O3 |' X, O2 s' L
- /* USER CODE BEGIN PRIVATE_FUNCTIONS_IMPLEMENTATION */
$ I" d- m8 S) M1 Q3 v - #include <stdarg.h>1 |0 ~( u# c/ t9 _! V
- void USB_printf(const char *format, ...)//USB模拟串口的打印函数
; D9 Q! ~! ]' e: d' O5 H X - {
, b# `1 s* m8 ? - va_list args;. }7 x& Y* S+ J- d8 N0 Q
- uint32_t length;
# A0 G, P6 [' [& D1 K7 ]) a- w - va_start(args, format);
+ T- c1 b4 y2 P: V r7 I - length = vsnprintf((char *)UserTxBufferFS, APP_TX_DATA_SIZE, (char *)format, args);
% q' q. I" T! W! h - va_end(args);
`: ?% p! q7 v* Z# @) v - CDC_Transmit_FS(UserTxBufferFS, length);2 W _% s: P1 n' Y
- }0 C2 Z; h* Y7 k
- /* USER CODE END PRIVATE_FUNCTIONS_IMPLEMENTATION */
复制代码
, N1 E3 T& L+ A; t 【6】在main.c文件中,添加USB驱动头文件支持
" \" J. C) S4 a3 u0 K$ g* J- /* USER CODE BEGIN Includes */5 @ U. }/ d" l0 F
- #include "../../ICore/led/led.h"
9 A7 g+ O. e8 s. n) M$ f& Z' i - #include "../../ICore/oled/oled.h"3 M6 [+ v9 J# `5 J8 p5 g/ `( r! x
- #include "../../USB_DEVICE/App/usbd_cdc_if.h"
( C' R& l% M4 [+ D# N - /* USER CODE END Includes */
复制代码
2 n8 h" F3 m) ^$ P/ K u, F 【7】在main主函数初始化处
# O! [' K$ Y/ A! K) @9 Q$ K- /* USER CODE BEGIN 2 */
5 F" S W$ J+ p4 B! w/ | - OLED_init();
+ K/ q% N6 [6 p$ z8 j: |! x! l0 O! a- r - //设置OLED蓝色背景显示
5 i, T+ L5 W1 L" b - BSP_LCD_Clear_DMA(LCD_DISP_BLUE);
. t# u z8 x8 ?8 h" v' s - printf("OLED_Clear_DMA\r\n");
$ f, [" d0 F; b5 }' z - /* USER CODE END 2 */
复制代码 3 U) `, f$ \% z' K
【8】在main主函数循环体内实现
) G4 \$ `7 c, H) x& o0 z$ O- /* USER CODE BEGIN WHILE */
0 z: O" h' i( O" | - while (1)9 x8 _' M1 {7 ^; f1 M* t# D
- {+ O6 N3 O0 j) `. Q1 |
- //USB模拟串口的查寻接收处理(其编程原理与USART1串口收发相同) T/ f2 {( i4 ]: l; M9 k
- if(USB_RX_STA!=0)//判断是否有数据
9 s& J; W! t# `# _) Y - {4 S$ H. t& b: [' r$ S$ H; P0 c
- OLED_printf(10,42,"%.*s",USB_RX_STA, USB_RX_BUF);3 p7 Y H1 N, D7 c# C
- Toggle_led1();
) P& e( r) x* R* e4 d - USB_RX_STA=0;//数据标志位清05 O/ M' w* e. ]; x
- memset(USB_RX_BUF,0,sizeof(USB_RX_BUF));//USB串口数据寄存器清0
+ H7 V! n0 [( f# y2 h( @ - HAL_Delay(10);//等待: a/ v. j' g: D# G7 W1 r: t7 z
- }
, ?' s) A q* a1 k( V3 S( ] - /* USER CODE END WHILE */
复制代码 ' c; x1 Z8 T+ p* K9 ?
四、编译及下载
/ I. S1 T/ b2 ]1 b( H 编译下载,该开发板原理框图,需要将跳线帽重新插拔设置,相当于原理的USB-ST-LINK连接到USB_MCU上,即原理lpusart通信及ST-LINK下载的USB接口改为连接到USB_MCU的DM/DP引脚上实现通信。
h- Y1 K5 A4 i( z' @
: V4 x6 E+ E9 E- k n8 ]6 Z8 F
' A' O/ Y( [( Q) m8 e8 N# a2 c2 [ [7 p' y6 Q$ [8 Z6 R3 @
更改调线帽后,打开串口工具,发送数据,顺利在点(10,42)位置绘制输入文字,LED1等会切换状态。& A, W* O3 J. l/ t
% G& s, }9 j9 }1 d
2 k9 U6 @* Q* y: a1 G: Z
3 @2 g. P# t, R' G1 x3 L5 b————————————————7 W& _8 e$ k0 N+ Q4 k6 p; }- e8 v
版权声明:py_free-物联智能
8 z& l- p- t6 r8 h0 ?如有侵权请联系删除
. h/ u7 M6 J2 n$ ^& Q; Z
9 W# w7 u; T4 p7 {
; m0 C1 w9 U7 r8 F) J |