学习stm32已经很长时间了,但是一直没有过多的学习stm32的USB部分,因为实际工作还是用的比较少。说起USB那就有的说了,因为USB的功能很强大,这里主要重点记录一下STM32的USB部分,这个官方给的有专门USB库,笔者目前使用的是Cotex-M3内核的STM32F103系列,实验的芯片为STM32F103C8,这个是目前市场上性价比非常高的芯片,也是用的非常多的芯片。
6 `- x. ]2 i) A. y* y% C/ j3 b' L* N3 k- c$ b
USB基础知识. n+ [ a8 ?, O/ I# \/ A4 ]7 C2 p( k# y
USB按接口类型分
, X- ?" H. F( J2 e+ I1 N控制器/主机(controller/host)
* ?9 w& V+ t N1 X& O, r1 V8 L5 |6 s+ T o) q6 c- v
设备(peripheral)
* v: z6 Z! M, _; s9 K4 M9 p- u* V, n
OTG(on-the-go),通过id线确定作为主机还是作为设备
& I* q5 X: y" ~# L+ ~% Q" k* W+ _5 [4 m% E5 l* w0 w% J
按照USB速度分
3 b( N$ j. _; [$ q2 Y6 X) d5 j6 w5 o: w" d F3 M* m
低速(low speed)
/ L7 U% h; x; n1 r+ w+ B9 c" q% a x- I8 e& F2 S. J6 b1 y
全速(full speed)
4 |2 ` t) o1 P b; |4 ~* P* J/ D! x- s6 s2 V; Q/ V: S" e
高速(high speed)9 P3 z, C9 n6 c. U F9 s
: g, Q c. S7 q5 m6 |0 w
USB接口一般是4根线,VCC GND DM(D-) DP(D+)! W3 y) n7 m% N4 ^$ V! u' J
& E% C5 W, }& C1 i& W4 ~ Y/ D5 J低速设备:在DM线上接入上拉
/ g E$ M2 P6 t- ^/ s) v; U$ k, w, W; t
全速设备:在DP线上接入上拉( i7 L& P/ i4 B, C! l
! G& u; P' g! i8 F7 v- D5 N高速设备:在DP线上接入上拉,在主机对设备进行复位后进一步的确认+ U: l" o3 S/ C. F3 Z" p6 ~4 o& a+ @
: H$ |; x' W% N2 g/ r
- Z+ h9 C& v7 L ]/ u! v关于描述符 a- B% s' V, D* y t! J
设备描述符(device description)
, Q$ |4 q# U' `( Z
# G8 e( X# U7 ?) J$ z! e7 p3 L j$ ]配置描述符(config description)4 \: P) {5 W, \5 a' F
5 O; w0 m9 G! d# u8 c& E( m% k接口描述符 (interface description)8 z3 `! h/ F+ N" }6 G8 v+ `* x9 `
4 F) \0 E. v. f! H- `
端点描述符 (endpoint description)
6 S9 j7 o) I: n, _8 u
0 p' c8 }$ {9 T6 [6 Y- S
K# I* z+ }* W$ Q
7 j* @% o f9 G# ]设备的“身份”信息存储在描述符中。每个USB设备中都有如下描述符。需要注意的是一个USB设备只能有一个设备描述符,一个设备描述符可以有多个配置描述符,一个配置描述符可以有多个接口描述符,一个接口描述符可以包含多个端点。+ [3 n2 u4 z9 C% Z
7 |$ E" J. M2 F% q4 R h" P7 h
关于传输0 o {7 m8 U2 {7 d! x
1.在USB的通讯中,有传输(transfer),事务(Transaction),包(packet)三级。包是最基础的传输单元,与TCP/IP协议中的MAC层协议作用相同。, z$ k& Q6 R5 C2 L' L2 p- g$ \; G; e
2.在一次传输中,由多次事务组成,每次的事务又由多个包组成4 i N4 }" q: J' Q" T) B
3.与众多协议相同,较高级别的协议的报文是基于/内嵌在低级协议的报文当中的,在USB中也不例外,例如,包中预留了DATA位,其目的就是填写报文. N) f6 J! t( }
STM32F103芯片自带了有USB模块,可以用来做从设备,不能用作主机HOST。这里使用USB的目的是讲USB作为一个大容量,这个可以基于官方USB Mass_Storage例程来移植。这里就不过多的介绍里面复杂的通信原理了,要彻彻底底搞明白并灵活运用取来还是有很大难度的,因为内容太多了。这里仅仅介绍下移植过程和描述符相关的重点内容。
; e6 e: X* G+ k: t( i8 k: m6 G/ [$ E/ i, m# n) g
准备工作' l# K1 N! ~* w' C* H) |
第一步( {! v5 l( _8 y" d7 x+ S
确保自己的KEIL开发环境已经完善。; [2 w( ~% R5 J0 A( w
" `+ M* G* c% S1 x6 z# c
第二步0 j& {8 ?' J, ~7 v
下载好了两份ST官方的库文件(我是基于标准库开发的),一个是标准外设库文件,一个是USB库文件。如下图:- S9 z$ w. ~% H6 M$ Y5 M: P9 N
6 l2 j' i* B: j1 |
) D; ~2 A/ n% m3 i M. U, P3 O; f) H* z
第三步
8 V2 I% y' ?% m- |; a. @建立一个带有存储介质驱动的STM32基础工程,存储介质常见有SD卡、外部FLASH芯片、内部的FLASH空间。的我是基于一个外部flash的工程去实现的,芯片具体型号是W25Q64,64Mbit的空间,换成字节就是8MByte。驱动部分如下:$ o2 R$ P2 M2 B; X1 e3 I
$ O, x# x1 e7 N7 s9 h( p% G# ?* W/ e
C文件
+ n5 B* } O B/ m3 y5 G$ s2 w- #include "fy_w25qxx.h"
N) r5 {# z( h( V8 X) s& t
1 \% @$ U6 L, A' {+ Y5 n- u16 W25QXX_TYPE=0;//W25Q64
4 r- l+ g9 y! |- b# P8 ^, L7 X
4 U, A4 ]4 u. P% j5 v( d' r3 x' v9 i- //4Kbytes为一个Sector,16个扇区为1个Block,容量为16M字节,共有128个Block,4096个Sector
* j$ L$ [% Z& W - //初始化SPI FLASH的IO口6 B9 E2 t: S2 z- w
- void W25QXX_Configuration(void)6 A# Q) S% u" X, C
- {
3 J$ ^4 o( Y) C2 ~$ T5 W9 ?1 V - SPI2_SetSpeed(SPI_BaudRatePrescaler_2);//设置为18M时钟,高速模式; t/ G: h* F3 g
- W25QXX_TYPE=W25QXX_ReadID();//读取FLASH ID. : }% V( K( `3 R4 a, }; G
- }
; `( d( p2 D3 k S - 9 [; Y6 T8 Z9 j
- //读取W25QXX的状态寄存器& L0 x$ O6 {6 X! c( y5 l
- //BIT7 6 5 4 3 2 1 0
5 @+ o1 s4 T6 y& c! E+ w - //SPR RV TB BP2 BP1 BP0 WEL BUSY' r- m/ ~' e: V9 W2 g5 _4 q6 T: @
- //SPR:默认0,状态寄存器保护位,配合WP使用- K {# W. j; P5 G% k v! K1 U
- //TB,BP2,BP1,BP0:FLASH区域写保护设置- D1 |5 X4 }! g2 i* N w
- //WEL:写使能锁定
: d( s \+ a& {9 h - //BUSY:忙标记位(1,忙;0,空闲)
/ M& r7 o/ j/ V9 D% A/ G - //默认:0x00
: _0 |8 r8 `5 Q7 N, b - u8 W25QXX_ReadSR(void) ' |( y! t' o3 M
- { + A: a3 _6 x, f& F) V3 K2 O- j7 ]3 s
- u8 byte=0; ' U% `: S9 u/ V |
- W25QXX_CS_L(); //使能器件 6 j8 Y0 ~4 I' J- W; @& Z+ C, u
- SPI2_ReadWriteByte(W25X_ReadStatusReg); //发送读取状态寄存器命令 + ~& @' g; Z, D" W: O2 Q* v+ p3 T
- byte=SPI2_ReadWriteByte(0Xff); //读取一个字节
7 z# O5 H% {; S - W25QXX_CS_H(); //取消片选 1 w" I+ U' u* d" ]0 o' f" ^- j2 @
- return byte;
( W% K, i& z4 b4 L9 Y- Z0 q3 c4 n - } 8 m* H# ~ z( X; G- D& t
- //写W25QXX状态寄存器 B- a6 [8 [* j |0 s7 P# ~
- //只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
/ E' Z: |- D. o3 ? - void W25QXX_Write_SR(u8 sr) 9 R! v \8 L8 P6 a4 |0 n" K
- { * i# V% K3 x1 m
- W25QXX_CS_L(); //使能器件
, E$ K/ H2 X9 g0 t1 o - SPI2_ReadWriteByte(W25X_WriteStatusReg);//发送写取状态寄存器命令 % }; Z; _5 ?; s+ |' X9 f
- SPI2_ReadWriteByte(sr); //写入一个字节 9 n+ m( f `+ a+ Y4 `
- W25QXX_CS_H(); //取消片选 / R V) J6 |! ]4 K. S0 m$ i5 l, r
- } 2 E1 J7 D5 x# \( o: S
- //W25QXX写使能 3 c6 }3 ?- p3 H: A, N7 x
- //将WEL置位
6 D- v2 v7 F7 k8 J e8 b - void W25QXX_Write_Enable(void) - N) r$ @) @# \. X M7 p
- {% G6 G( s' N3 ?4 p
- W25QXX_CS_L(); //使能器件 # e! O3 z% |9 j% ^4 S- `
- SPI2_ReadWriteByte(W25X_WriteEnable); //发送写使能
4 l( ^% _0 f9 G* V x1 l: @, U - W25QXX_CS_H(); //取消片选 / c m) Q; K% ^* W+ Y6 O
- } * u. E0 k3 k3 v) k# j/ V+ i
- //W25QXX写禁止
# d% ]& v- U2 y2 f2 O5 V7 M - //将WEL清零
# ~* S. h" b/ M/ ?0 p) j! A - void W25QXX_Write_Disable(void) 1 X8 D, L/ V* D, l
- {
1 m0 t2 R+ e4 d7 G2 r7 K* n5 | - W25QXX_CS_L(); //使能器件 D& m) A4 P: I4 m% B
- SPI2_ReadWriteByte(W25X_WriteDisable); //发送写禁止指令
0 S7 z V" b% K$ a* T - W25QXX_CS_H(); //取消片选
9 d; K% n* k2 M2 p1 ^! R6 K - }
$ H8 W* _8 J2 h1 r" o6 M
( k8 ^- Y3 J! R# g% @- //读取芯片ID
, D* k3 A' m9 k* g - u16 W25QXX_ReadID(void)- ?' L& D H, D1 b
- {7 y: |- Q- r9 ]* @8 q
- u16 Temp = 0;
! [% b2 t5 ?8 E) e' _ - W25QXX_CS_L(); # j( O# o; g1 Q4 w" x% O8 c( P
- SPI2_ReadWriteByte(0x90);//发送读取ID命令 1 [# x, m, l/ P, u: w7 B
- SPI2_ReadWriteByte(0x00); & c- x/ r% k0 ~4 o
- SPI2_ReadWriteByte(0x00);
2 f# c) s5 Z- L. u# l0 v* b: m4 o - SPI2_ReadWriteByte(0x00);
+ i% f& Z7 J* S$ y- I$ G/ {4 D - Temp|=SPI2_ReadWriteByte(0xFF)<<8; 3 ?) S1 N' |2 E5 K
- Temp|=SPI2_ReadWriteByte(0xFF);
0 u" f4 v; D9 H7 ^( w0 _2 u - W25QXX_CS_H();
. `3 {; u: x) A/ d, V! } - return Temp;
. M3 j& m: G+ n5 G# m- S( T0 Y - } 6 z5 _% l9 f; F) H
- ' B7 [6 ?% i3 e3 P. \. J8 m; P
- //在指定地址开始读取指定长度的数据( `5 E1 p; I$ S( e, T
- //pBuffer:数据存储区,ReadAddr:开始读取的地址(24bit),NumByteToRead:要读取的字节数(最大65535)
* S8 p! v9 l3 C/ g: i - void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead) + ?, N, k+ K! B3 t5 v
- {
$ b! B9 c( u2 m: {; |. L7 ] - u16 i;
7 E, @: c/ z8 J2 d) D6 h - W25QXX_CS_L(); //使能器件
, z* @& @( K9 \5 U3 l& Y% s - SPI2_ReadWriteByte(W25X_ReadData); //发送读取命令
* e" j% o# {2 t& p9 W4 E - SPI2_ReadWriteByte((u8)((ReadAddr)>>16)); //发送24bit地址 ( l8 W: B4 b6 h& e8 t- n6 _
- SPI2_ReadWriteByte((u8)((ReadAddr)>>8)); . l/ p1 S, l) v- j
- SPI2_ReadWriteByte((u8)ReadAddr);
2 R$ J/ w: z! @# o; P- f* @' d0 t; G - for(i=0;i<NumByteToRead;i++), E j3 r+ e! k. Q: a
- {
0 P" z4 C* p3 h6 x2 l9 z - pBuffer=SPI2_ReadWriteByte(0XFF); //循环读数 8 l9 e3 k j- L {0 z3 w; a
- }
5 G; D1 @" h# T - W25QXX_CS_H(); 1 [: l1 O+ @0 w2 l
- }1 K1 c' u; n: R/ c5 u0 L8 s$ @2 V$ P" }
- 5 s/ i! ` I. ]
- //SPI在一页(0~65535)内写入少于256个字节的数据+ r( T. b" ]* N0 _5 V% J! p
- //在指定地址开始写入最大256字节的数据; P2 w/ o6 q9 e. d
- //pBuffer:数据存储区,WriteAddr:开始写入的地址(24bit),NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!! 0 R/ v9 h/ M0 n" R. V, }
- void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)0 z8 d( Y+ S- Z6 p
- {
* u5 c2 D S7 w: B! t. u - u16 i;
/ a! q F4 N$ j7 Y2 z$ [ - W25QXX_Write_Enable(); //SET WEL $ a1 ?4 g2 s2 G+ Q+ T( g) A
- W25QXX_CS_L(); //使能器件 7 X- u; R, f$ M8 C9 t" w
- SPI2_ReadWriteByte(W25X_PageProgram); //发送写页命令
8 U* P8 P8 Q# w( h9 y a' ?0 o - SPI2_ReadWriteByte((u8)((WriteAddr)>>16)); //发送24bit地址
4 J8 c* O, C1 Q" u - SPI2_ReadWriteByte((u8)((WriteAddr)>>8));
2 W% K8 D4 j9 D# _9 ~) z% n V h, Z - SPI2_ReadWriteByte((u8)WriteAddr); ) i. y8 B% x- s
- for(i=0;i<NumByteToWrite;i++)SPI2_ReadWriteByte(pBuffer);//循环写数 " H9 y8 H8 o4 K' M$ a/ G/ p* N
- W25QXX_CS_H(); //取消片选 ( a# |& ]1 Q8 A6 C
- W25QXX_Wait_Busy(); //等待写入结束) \5 S5 |8 `0 V4 d2 }8 h' x
- }
% o& C% `2 q1 N4 S - //无检验写SPI FLASH
+ w7 A( _3 ^; l - //必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!7 W! ~; Q. ?* e& V# b+ N
- //具有自动换页功能 * Z4 J" ^. Q" H0 d& ~/ G( ~. q
- //在指定地址开始写入指定长度的数据,但是要确保地址不越界!9 _: Q% B6 l# I& x+ c2 o
- //pBuffer:数据存储区7 T, O. h, B6 r% L/ x2 Y
- //WriteAddr:开始写入的地址(24bit)! W5 l( H" {4 u1 D" Z/ `+ L8 u3 G
- //NumByteToWrite:要写入的字节数(最大65535)
; Q2 }$ ]0 \; P; {3 b - //CHECK OK
' ?, E( X& z) |. h4 g1 }0 s - void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) ) b+ v" W5 q0 V) f
- {
- S3 s* c6 I% R - u16 pageremain; $ s7 F% k1 m# y6 I- a+ R
- pageremain=256-WriteAddr%256; //单页剩余的字节数
$ Q# o5 \) z; K* K) p - if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节
# _2 T2 ]: y7 b, o4 |- f - while(1)
% s! Y* ~/ y! K; [8 l; x B1 a: b - { % V. U! j$ L+ C3 w2 ]. w
- W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);) h9 T+ ?- f3 O( q$ `& O" r' A$ m
- if(NumByteToWrite==pageremain)break;//写入结束了
. G: I2 @9 l* _3 L6 O - else //NumByteToWrite>pageremain8 ^- S) r7 Z9 Q. V" H- m. ?
- {
% i1 h8 W" A# u1 I% m& ~ - pBuffer+=pageremain;
' Y+ l( b: ~+ B - WriteAddr+=pageremain;
' K& t2 h3 z7 K# O& u) Q! c% \
" v% R3 ]& Z9 ?. k- NumByteToWrite-=pageremain; //减去已经写入了的字节数
0 X6 K" O2 {, l" I7 t - if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节
. h Q( b" Y, N% x _/ F2 M& J# a - else pageremain=NumByteToWrite; //不够256个字节了
x2 b7 W- H9 }: r - }
0 f1 V! }& X' \9 U" X [, Z# ?- } - }; 9 Y; r! f6 [2 ^6 ?' d4 U$ n, F
- } ; d3 n8 D* i: Q: }
- //写SPI FLASH ; v. g [: O+ ?; h
- //在指定地址开始写入指定长度的数据
& j% m: @- v% z" D, Y - //该函数带擦除操作!
+ s, O# s6 o* I* e8 u% x - //pBuffer:数据存储区
- T, N$ i1 w# h1 r - //WriteAddr:开始写入的地址(24bit)
N) Y1 c- g1 \% o - //NumByteToWrite:要写入的字节数(最大65535)
1 M6 |" V4 j1 L' y h# I - u8 W25QXX_BUFFER[4096];
7 d/ B1 Q- s+ t9 E9 | - void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) 1 s2 A, T5 C6 I' ?0 S E, H
- {
6 g8 x$ b% w/ n& p ~ - u32 secpos;
3 o. E1 N6 e4 p1 l- X8 H' v - u16 secoff;" f# \% }6 X A- @9 i k6 U
- u16 secremain;
. @2 E5 L$ g( O! o8 ?! z4 O) P I - u16 i;
, y: L- O6 G; U# U/ ?) D* X+ A - u8 *W25QXX_BUF; `, p Q% x' ~! ]; |" \* T
-
' l( I3 K7 y+ y2 P3 P( j: D - W25QXX_BUF=W25QXX_BUFFER;0 V2 B9 V) S" l3 N# V! U0 X9 X6 }
- // 5 L9 E$ e5 i _
- // W25QXX_BUF = (u8 *)_mem.Alloc(4096);//申请一个扇区大小的内存1 U0 @6 E$ B P
- // if(W25QXX_BUF==NULL) return;
! {7 W$ b# W" U9 }- e - . z* N/ T% O. c4 ]
- secpos=WriteAddr/4096;//扇区地址
3 s$ Q$ r1 d$ h% A1 {% z7 s* T( b - secoff=WriteAddr%4096;//在扇区内的偏移, ^+ J6 o% |7 h+ V4 {
- secremain=4096-secoff;//扇区剩余空间大小
( \* J% x$ Q( S. o
5 M) I4 }; c" }, r8 }% ^" X- //printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用
+ I$ v; l) Q R) p! \* i# r6 Z - if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096个字节* p) [4 w" _* f* Q
- while(1)
9 A k9 }) S T; G - {
) m2 e j$ J0 Q3 H \: W - W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容 r: W, K8 F# z6 y6 x& S" }
- for(i=0;i<secremain;i++)//校验数据
5 c3 ~' ?) j" s - {1 d" {! F3 g1 o1 M5 v# m
- if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除 f2 `! K6 K) V! C7 W) ^
- }9 O, U/ X9 B3 y
- if(i<secremain)//需要擦除$ l$ a9 G1 z) X. @
- {; W2 n1 b& u0 Z, I5 J' G" F( t
- W25QXX_Erase_Sector(secpos); //擦除这个扇区) Z6 V2 i7 _6 u+ {# ^7 l
- for(i=0;i<secremain;i++) //复制0 U1 n& S4 X& P- n% q+ W& S+ z
- {" g& w Z* d5 L4 g/ R- v
- W25QXX_BUF[i+secoff]=pBuffer; 9 x5 g9 L+ z9 A; ^ h9 g! d
- }- N" }# Z7 f J% Z
- W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区 3 \) L9 \ r" ?
+ q9 m; O7 B- p9 Q' L) o- }else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间. # `# u* i- m1 A; f0 c
- if(NumByteToWrite==secremain)break;//写入结束了- W. u; n& s0 }4 p5 g) e6 [' V$ x U
- else//写入未结束
1 o ]: @. w9 I# }4 a - {
j) C& ?' Q$ |+ I - secpos++;//扇区地址增1! p( T/ y. B2 C- E
- secoff=0;//偏移位置为0 * \' w! ~# c3 ]
- ) ?# F# k% V$ F' k9 Q+ K1 g
- pBuffer+=secremain; //指针偏移
0 i+ T; z! o+ E2 x; O+ e - WriteAddr+=secremain; //写地址偏移
8 X- w6 O) _; J F) Y. B3 [ - NumByteToWrite-=secremain; //字节数递减
* R2 Z. c; p0 J - if(NumByteToWrite>4096)secremain=4096;//下一个扇区还是写不完$ q9 f* u( w; [/ r! r5 e
- else secremain=NumByteToWrite; //下一个扇区可以写完了$ K& x8 |2 M3 n1 K, X
- }
& O3 P) M6 D" d0 D$ ~. _ - }4 Y# y4 D- k: d4 R
- // _mem.Free(W25QXX_BUF);
0 I* |8 @2 h" W: g, y" i - }
. j" ]* G9 s" I b/ Y2 e5 N - //擦除整个芯片
9 \) G( k2 Z9 q+ d$ S# I7 o: I& d0 u - //等待时间超长...
4 ]& |6 s. Y9 x3 {' {$ ?2 J" j+ i - void W25QXX_Erase_Chip(void)
# I3 x- h/ G5 {% [ - { * t% W( P+ H5 I: z0 o
- W25QXX_Write_Enable(); //SET WEL
% w- \) z7 o2 M. j - W25QXX_Wait_Busy(); , H7 K# S; ]* {% l- s
- W25QXX_CS_L(); //使能器件 1 Z# S# z2 i% M7 ?4 s/ B
- SPI2_ReadWriteByte(W25X_ChipErase); //发送片擦除命令 % `) \2 h+ h; E8 U1 d9 f4 B
- W25QXX_CS_H(); //取消片选
- P; V& V. l- t9 L - W25QXX_Wait_Busy(); //等待芯片擦除结束 P, q, d: f0 q0 A2 k7 X. c
- } 5 G3 H" G; O3 ^* ^: q: J& i+ M: m1 Q) M
- //擦除一个扇区6 F) V+ L; X* g8 Y
- //Dst_Addr:扇区地址 根据实际容量设置+ g( u( i. \- j) e, m$ {
- //擦除一个山区的最少时间:150ms4 l2 U) y/ M9 D4 k( Z. s' E
- void W25QXX_Erase_Sector(u32 Dst_Addr) 1 Z! e, W. T3 `5 z9 R, z5 [
- { ; G2 c$ E- X7 b! M
- //监视falsh擦除情况,测试用 ; r5 |' a. c: |& j; }6 A, ^" V
- // printf("fe:%x\r\n",Dst_Addr); . f; s1 Z% R/ i b# h: Z7 d" l
- Dst_Addr*=4096;, \4 f. C- g& S# F
- W25QXX_Write_Enable(); //SET WEL
8 @2 Z+ _+ e, T0 h - W25QXX_Wait_Busy();
, ?) b7 a) \' o6 b - W25QXX_CS_L(); //使能器件 ' k) r! O- w, c4 u Q" Z
- SPI2_ReadWriteByte(W25X_SectorErase); //发送扇区擦除指令 . b. |) y9 q% Y8 C, k: Q8 i
- SPI2_ReadWriteByte((u8)((Dst_Addr)>>16)); //发送24bit地址
8 {2 U- H7 }/ |* F3 |8 K - SPI2_ReadWriteByte((u8)((Dst_Addr)>>8)); ; Z% t9 z4 ?. A4 ]: K
- SPI2_ReadWriteByte((u8)Dst_Addr); 1 ^1 z- Q% g }8 B' K3 ^8 V
- W25QXX_CS_H(); //取消片选 ( k- t5 w* u7 w- s4 t
- W25QXX_Wait_Busy(); //等待擦除完成/ n8 }" z7 i3 U4 F) ]
- } ; E: S! X; s- f9 |! [- S* Z
- //等待空闲7 J- e5 c& j0 J2 k' E* F
- void W25QXX_Wait_Busy(void) ' X4 t" j% a! v5 _& R; p2 k
- { 9 V" Z, d: H; b/ k9 [
- while((W25QXX_ReadSR()&0x01)==0x01); // 等待BUSY位清空
% u0 k, `. O! l- {# Z - }
* j/ f% R# f' x3 m - //进入掉电模式
3 x. m3 y5 t8 M! P1 Z - void W25QXX_PowerDown(void) # H) b/ q+ ]8 d1 v
- {
1 c2 `. v' E0 \4 m8 M) c% h1 H - W25QXX_CS_L(); //使能器件 8 i5 E/ R$ u. B. e0 U# B
- SPI2_ReadWriteByte(W25X_PowerDown); //发送掉电命令
. J" m0 \% ~1 F/ I1 [3 V5 ] - W25QXX_CS_H(); //取消片选 o3 C0 R0 M/ ], z' H' x
- Delay_us(3); //等待TPD 3 \6 r0 t% A# G! E3 f
- }
/ |$ C- a: }2 F7 o" D& A7 N - //唤醒% C/ p+ O5 k3 S1 {# e9 v
- void W25QXX_WAKEUP(void) 1 Y" }0 g2 ^# Z2 ]3 r) I. L' |
- {
/ v0 l2 ^, x! e( g2 n - W25QXX_CS_L(); //使能器件 6 z, s2 A# g' {' b
- SPI2_ReadWriteByte(W25X_ReleasePowerDown); // send W25X_PowerDown command 0xAB
* @; [! K2 X, Z- w5 K9 L6 {. I1 c8 e - W25QXX_CS_H(); //取消片选
' W. |! h. ^! s. `+ Z/ C - Delay_us(3); //等待TRES1
' r1 m- ]0 U7 y4 p- R - }
复制代码
2 y& U- f" |- [- |$ k8 OH文件
& S' `$ h$ V* E! l% s, m- j. \4 m4 V, G; W
- #ifndef __FY_W25QXX_H
; v. l1 Z8 k1 L% Q' D - #define __FY_W25QXX_H / p3 Q6 h* `& J# K
- + f- k) z3 w' Z
- #include "fy_includes.h"
1 E# }5 _. f$ h5 a$ `7 O% z U' K - % P0 u$ P$ K9 `' G& X0 S
- //W25QXX对应唯一识别ID
+ _3 `! P* w! ]5 ~ - #define W25Q80 0XEF13- m, \1 G5 _1 F% R i4 u/ Y
- #define W25Q16 0XEF14
' s+ l/ G6 Q# c* U1 J$ y - #define W25Q32 0XEF15
9 [3 f c" j5 Y. S4 m1 v - #define W25Q64 0XEF16: S, k, D: c) l
- #define W25Q128 0XEF17
: b$ B! o; L! I' s6 L$ r) x5 t3 e. y
2 I/ x$ U, z* L' ^" I, i! s- //指令表
2 m5 O# g/ G9 V9 o2 t - #define W25X_WriteEnable 0x06 / J3 A% ?; v/ P4 Q' B0 y
- #define W25X_WriteDisable 0x04 : p7 b& I6 X1 b1 P. m- `5 B
- #define W25X_ReadStatusReg 0x05 ) { W% p( q* m- k+ ?+ t1 y( V$ l& P
- #define W25X_WriteStatusReg 0x01
+ N, H& i. T2 S8 O2 c; Z6 D& c - #define W25X_ReadData 0x03 / ~1 `- @+ y& Q8 H& ~) L- C
- #define W25X_FastReadData 0x0B
: a. g7 [! ~9 W- A - #define W25X_FastReadDual 0x3B
* Z+ c) O0 Y6 @" h( y( A - #define W25X_PageProgram 0x02 8 J( I! B9 f/ B8 k. c$ [0 k+ J
- #define W25X_BlockErase 0xD8 - s& Z. w' S( ]7 ]( t
- #define W25X_SectorErase 0x20 & X! Q3 r/ ]/ u; j d! {* O
- #define W25X_ChipErase 0xC7
& a' } @$ w6 Q( b3 @, m& C% R - #define W25X_PowerDown 0xB9
. {* D& ]4 z, E5 O6 |2 K - #define W25X_ReleasePowerDown 0xAB 7 M& L, ]$ R4 ?- h) b: F6 M/ ]
- #define W25X_DeviceID 0xAB . W( C D( m9 p# A, q7 P
- #define W25X_ManufactDeviceID 0x90 + t5 e; S7 q/ A4 R; u! ?
- #define W25X_JedecDeviceID 0x9F
4 M9 @& Y, ~1 g' g3 z9 j u( I - # K( x4 u" n' e* V
- void W25QXX_Configuration(void);/ A! _3 Y: b5 r* X
- u16 W25QXX_ReadID(void); //读取FLASH ID
8 k' f2 ~( o. }- D3 P9 w' { - u8 W25QXX_ReadSR(void); //读取状态寄存器 8 P x g2 }6 r4 u$ j7 j
- void W25QXX_Write_SR(u8 sr); //写状态寄存器
% k* Y' b; b0 f - void W25QXX_Write_Enable(void); //写使能 - O. [/ W6 `* r3 n7 ~! D
- void W25QXX_Write_Disable(void); //写保护
5 C Y0 W) q7 K4 F( W* V - void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);4 n' N. f" T' e% B
- void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead); //读取flash
+ U% g9 {7 W: x% C& S; V: B2 H - void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);//写入flash
2 j& d- l, n8 U5 t' m9 D - void W25QXX_Erase_Chip(void); //整片擦除
+ `: w. f+ S8 x" e1 p - void W25QXX_Erase_Sector(u32 Dst_Addr); //扇区擦除/ [! ]; C( l4 g/ \3 b
- void W25QXX_Wait_Busy(void); //等待空闲
( A& K" d% J6 K1 `' y i9 } - void W25QXX_PowerDown(void); //进入掉电模式
. o0 W: P6 L [ M! ] - void W25QXX_WAKEUP(void); //唤醒
3 K, ~/ m* G# z+ w8 r o C6 [
$ [# |8 U& r: }7 J) `* t# H- extern u16 W25QXX_TYPE; //定义W25QXX芯片型号
/ w+ e7 w0 f E' k
( }7 i; \2 y" _+ }) _ q# V- #endif
复制代码
+ O7 `) l2 u. z( e/ {. q4 n基础工程如下:
: v3 {2 x# q2 L+ G
* E+ M" O' D" X
2 `1 \6 l9 A# b$ e' A4 E# G/ F% Z& P. r
第四步:USB代码移植3 g4 ]9 b9 I0 U- y, |
拷贝USB底层库到工程根目录,新建一个目录命名成USB,拷贝Mass_Storage例程下的src和inc目录下的所以文件到USB。
: N. \) a I& Q" O
, i0 z9 t" c/ _* x8 E; C% G u3 b
( h- v5 C1 r) @% c
* Z8 `( H9 v+ p: I# c8 [
拷贝过来是有37个文件,这个时候需要删除一些不必要的文件,因为官方的例程里面有nand,这里的话先删除以下文件
2 |& t* m) y9 y% D% ]( u2 I5 a" @5 @$ K$ H% ^2 V' p
# V9 F L0 E+ c+ _7 ^
/ i( J/ f# l, I3 l6 ^8 M) T9 D
4.打开工程把分组和文件及包含路径添加进去,这里就不细说了,结果如下
$ n: B9 L( X `
, X* a% V8 [/ k$ V. L
: a4 s/ p5 ~2 Z9 ?: U, u5 |5 \6 v6 M6 O4 i( u; f& |, k' F# z9 @$ ?
打开usb下面的main和it 把重要的代码先拷贝出来
- n. a1 z; S3 ]& G% r" g; h& C3 w5 V$ A# |$ m, ~+ e
2 E3 O1 ~1 o8 r
; i W2 P; s+ Q4 ?& I1 |" q' [) I把main和stm32_it里面的内容拷贝到自己的main里面进行下封装去掉不要的保留剩下的,然后就删除it和main三个文件。8 w. r5 r5 m: R: T2 K
! g2 Z' [( g3 v& s
8 [8 _3 ?* e8 L8 F* Z1 r0 |# ~. ^# A
( W2 z$ h) z' a% D然后编译下,这个时候会出现一堆报错,需要慢慢一步步搞定,编译前注意路径包含,' E1 p8 H6 n4 Y' e' |4 P0 ~
* g. I6 s+ @# w# j; J" C& T$ E
, H9 w: O6 s) Q( `3 x; s& C
7 B; S: I& J0 a2 r. Y$ X
定位到第一个错误进去: L- p+ W9 p0 n! K/ ?
+ F: ~' r* ?& d! R
% a" r: ?$ {: L7 X
2 }: V0 O; D7 m$ {( |8 b4 Z这个定义的是官方的开发板 我们直接修改成自己的公共文件就可以,并且注释掉开发板定义1 Z- O2 }6 [) |# A& S5 d7 `
% H% s1 `: K. w. J
$ D9 R) ~; `% _3 _8 y1 X
2 f6 J: [) C0 Y公共文件如下,并且添加usb部分的头文件1 [- v7 N9 Q3 Y3 G& s
$ S, [$ |" e# V2 L2 c: `
. R- K. c# b/ w! K! i$ x
Z, c* V V7 I5 D* S: ~. @
继续编译,定位第一个错误,这部分主要修改hw_config文件( t; @9 N0 [& B+ c! [, { p
: E9 \6 ]1 D3 F6 f( Q# J
: V* _3 N8 g+ j4 G5 h8 Q# L1 S( I5 n- l7 b, S2 M' {% d
删除Set_System函数; i, m& r. A0 g. i% f4 _/ Y
. k. ^) P& U- @( ]. J) c- a$ z
8 R1 n* v( Q4 F
* N* H# S( R3 F! g& \删除Led_Config函数
3 v* q& L3 ^; B, n
! _5 b% T7 w7 p; a
) [2 y% ^" @* B7 P, h2 k# V7 n$ n+ Q. q; y
注释LED相关的内容
0 d/ f8 ~! h6 R9 k$ M. g2 P6 `; \% ?+ U2 C) I# Y/ h) L
/ b! t. z' j; T7 V3 E, e" M0 o8 E9 S" g( m W2 E- Y
删除USB_Disconnect_Config
% b1 c0 t8 p+ P8 H
; i" ?" J8 _0 ]4 w1 _+ O
* K4 T0 R% l5 [& J
4 t ]& c: y6 d5 ?
接下来主要修改mass_mal部分,我这里是只有一个flash,所以我修改成如下:) H- S: ?/ i4 m" P/ p+ N
* W* R6 t# v# } B) V/ V
7 l( A# ^5 }& {( ~6 ~1 ?8 l9 y
' l0 a8 I$ G# M u) C N V; j; f头文件
# L, y: o9 S0 G2 G: j/ B( T* L7 o4 e7 N$ _; y
" f# Y, W. M- g7 ~. L; M
* _3 U; |( V0 l7 l2 Z
下面修改memory文件,主要修改变量定义,C文件和H文件分别如下
4 v, W* m1 r j7 X
' J. O' O# c) T& s( x1 t7 ]! y2 e3 c' @" }
' @" Y' a0 e5 o6 w$ @* V' M% u# y# X
接下来是usb_scsi部分的变量定义, g+ ?* v2 [2 M4 X. |2 @$ h0 O2 i
0 _$ v; `/ }( o) ?( w! C
' ^2 B6 Z. Y5 V
( V" W% ^2 e% N这时候再次编译就发现没有错误了,但是这个地方USB中断部分还需要配置下,官方用的宏定义方式去实现不同的代码,这里一开始就去掉了宏,所以最终修改如下:$ A8 c/ f* P6 B7 f/ I
9 E0 C5 g) X$ u; y- }' ]+ ?2 i
- /*******************************************************************************8 u9 `# e- ]* z3 f
- * Function Name : USB_Interrupts_Config) \* T) K8 L% x3 \
- * Description : Configures the USB interrupts
& s, G7 M- i, ~( t+ ^# ^- B- { - * Input : None.
( w0 j9 o1 @; V- Q4 \/ e - * Return : None.
6 k( s" v6 T* n& m5 d - *******************************************************************************/
% ^7 v# I; d# h - void USB_Interrupts_Config(void)3 ?, o8 t7 I/ ^5 J* v! ^$ _8 i! T" q$ C
- {
( l! W9 n1 E+ `- Z' I -
0 i' X% v; b5 Y$ Y+ Y, w - NVIC_InitTypeDef NVIC_InitStructure;
: l6 C& W, S0 [+ r- V! g- [ - EXTI_InitTypeDef EXTI_InitStructure;
9 a" Q6 D& ^! f4 o5 v6 X8 j' W8 W& `2 y - $ D. ]0 y2 P8 W+ I* L& z, D
- ! m& o3 K9 p3 Q4 x) R
- /* Configure the EXTI line 18 connected internally to the USB IP */. e! L) \( Y- Y H: Y) y& @
- EXTI_ClearITPendingBit(EXTI_Line18);
8 d9 z! [) |% V, f5 ^ - // 开启线18上的中断
) m" C/ O* H' K1 E - EXTI_InitStructure.EXTI_Line = EXTI_Line18; // USB resume from suspend mode7 r% r: _3 i3 W
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; //line 18上事件上升降沿触发
( ^2 Y) G0 O1 D8 h' D5 m - EXTI_InitStructure.EXTI_LineCmd = ENABLE;6 K; |& G5 |) K$ b( l
- EXTI_Init(&EXTI_InitStructure); + X! x" a* P, N$ w4 G
, [9 B/ t; Q& V' ]- /* Enable the USB interrupt */' P! e: J) u+ m7 A9 d4 v1 ?
- NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn; //组2,优先级次之 * R3 l7 p4 D9 s) k
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;% d. {! _, V) m- n/ C
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; j; x+ P8 x: r
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;9 r! O8 w. v+ E
- NVIC_Init(&NVIC_InitStructure);9 n( g# G0 @8 `5 W2 c
-
3 A0 p/ `* [& W- n: z5 ` - /* Enable the USB Wake-up interrupt */
* S( O, q( t: I1 y - NVIC_InitStructure.NVIC_IRQChannel = USBWakeUp_IRQn; //组2,优先级最高 , t* @( m9 P; c7 |" r
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
) C$ e7 |# G# N - NVIC_Init(&NVIC_InitStructure);
; o2 Q& }% `1 t# m4 v0 n5 D - 7 I0 m) l9 {2 Z6 [& m( E! L
: j- {6 M( ]2 u9 c3 ?( }- k! q- }
复制代码
; [3 e- W+ C& S# I y' |* v$ I6 b再注释掉USB_Cable_Config的内容(DP上有1.5K上拉电阻)并修改中断函数/ u* q: Q& Z6 k9 y. @# D9 v1 B& q; E
3 Y7 v# y$ K7 o- f8 l
- //USB唤醒中断服务函数7 E6 j- I: t$ n2 n, d0 G7 s
- void USBWakeUp_IRQHandler(void) ; `; O/ a [3 s* H% M' Q6 l5 T6 s
- {
7 w9 E+ K) W' Y& T6 I% h - EXTI_ClearITPendingBit(EXTI_Line18);//清除USB唤醒中断挂起位1 r; R% G+ b4 @: m; h& [: A
- }
复制代码 9 p0 d$ m# y7 b" [
修改pwr的Suspend内容:
( d2 E9 h: U! G$ }9 ]; D+ ?* E5 ^8 y _) s" ~0 t/ j; d( U- Y" @
- void Suspend(void)
% z( {8 N* Y$ m: ^2 H - {
$ U) E( f9 ~' T& [$ C! u - uint32_t i =0;/ w+ Q+ Z( ~8 ?2 g/ o, W* l# x
- uint16_t wCNTR;
- s6 S( S) Y% V. W" w4 m6 E2 M - __IO uint32_t savePWR_CR=0;( A2 T. {3 k: U. k4 ]9 r
- /* suspend preparation *// o1 H' I# E- g+ p
- /* ... */
3 {& q9 Z/ j5 z* m3 g) a -
W W+ s0 @3 G6 D3 X5 o; ? - /*Store CNTR value */" k* Y6 [* A; f _3 }
- wCNTR = _GetCNTR();
9 b5 ~7 ~- ]* F3 X4 @; u - % M8 T9 j8 F- X% b# d
- /* This a sequence to apply a force RESET to handle a robustness case */, f" w6 L% H4 f+ J7 U' d
0 A+ C- G+ _# u5 d: \5 v1 r3 W3 ^- /*Store endpoints registers status */
: ]( B7 R$ y a7 O/ N8 e* v - for (i=0;i<8;i++) EP = _GetENDPOINT(i);
& J( d5 f' i, }3 e- O2 r -
B+ |8 I7 t: U9 J6 f - /* unmask RESET flag */
4 Z) b% E" q6 ^4 s3 d J - wCNTR|=CNTR_RESETM;
: K* }; j/ n) m6 [, L$ H - _SetCNTR(wCNTR);
3 h7 L4 F! Y% R8 U( x6 S -
6 P" O( X( |' | - /*apply FRES */
& E2 Y# ^0 _+ A S - wCNTR|=CNTR_FRES;4 ^) O% k ^+ N& P3 _9 S
- _SetCNTR(wCNTR);6 E2 K8 N1 l9 T
- 9 h' g7 V* f7 a' j) ~$ Y
- /*clear FRES*/. ~7 C5 I1 d% z) d9 U# o2 G5 e
- wCNTR&=~CNTR_FRES;0 \4 G1 F! W) V. `
- _SetCNTR(wCNTR);
% U7 l9 t6 |, u6 A E8 v+ g; u2 y9 } -
g/ D7 C- I `& e - /*poll for RESET flag in ISTR*/
9 P& b1 @" g$ ?% R/ k - while((_GetISTR()&ISTR_RESET) == 0);
: N0 C0 ~! C; _7 x% P2 [" u1 I - 6 O& ^* t2 j. l2 F" S% U w
- /* clear RESET flag in ISTR */" f o" o; z9 S2 D1 ?* ]
- _SetISTR((uint16_t)CLR_RESET);/ X$ g7 l2 N; S* G
- # R- s) v# o. ~; R I( F
- /*restore Enpoints*/0 @, b7 m# }1 Q1 ?# i
- for (i=0;i<8;i++)
' j8 L7 ? u% T - _SetENDPOINT(i, EP);
( e$ a5 m3 W) z - % O' [+ Q! I8 s4 n
- /* Now it is safe to enter macrocell in suspend mode */
; M2 n% e0 m1 e7 D - wCNTR |= CNTR_FSUSP;# K* _* S& o; E; M. U) _6 _
- _SetCNTR(wCNTR);" E6 _9 f' A5 N5 f" i. |
-
8 d% c$ ^6 _5 }6 |" }! s - /* force low-power mode in the macrocell */3 a1 ]6 {' z5 M
- wCNTR = _GetCNTR();
. ?1 r) E! o! N1 A$ S1 h - wCNTR |= CNTR_LPMODE;! g5 C) L; s S$ H; i
- _SetCNTR(wCNTR);- U9 \9 L. ?( o% ~3 {/ d% U
- : V2 s7 X0 P7 _' q
- Enter_LowPowerMode();
7 a0 `) Y% w; \6 y- [6 ~; u - }
9 f; h0 { R8 R* M3 u
复制代码
7 C( |' @& w ?4 V. T d把USB初始化部分不要的注释掉:
3 T- a$ x ?* c" y. Z+ W5 O) s. V9 B- S- U4 E* T# @9 O5 D
- void USB_MSC_Configuration(void)
7 N( L9 W* N$ U$ v8 m - {$ N3 E, o& B6 x/ E
- // Set_System();
% q6 D( i. U2 a% N/ i( E - Set_USBClock();$ k: t5 W* F$ J6 x4 r3 e$ L4 Z
- // Led_Config();! t+ ^6 u0 l$ F" n9 V# D- `. Z
- USB_Interrupts_Config();: _1 `: B0 k0 s8 U( s
- USB_Init();
1 W/ D. Y: [) U- A! L4 f - // while (bDeviceState != CONFIGURED);
( g/ s! n7 c7 U; M9 _; G% W& y1 Y - 8 l7 A; a: u2 l7 }( D
- // USB_Configured_LED(); H2 }/ H0 |! O) z( M8 D
- 9 P+ M( a* e" p
- // while (1)1 R% E4 h: ~2 w0 t% u- r a
- // {}0 `3 y$ N+ ^. b: M: j- j$ g
- }
复制代码
' {. W7 h3 g/ n% {- [1 t9 V最后在USB初始化之前给U盘定义的数组赋值,不然就只有个盘符
# S9 q9 e( } K, Z
. j. `( i% d$ F- L3 @8 m: T, ]- Mass_Memory_Size[0]=1024*1024*4; //w25q64->8M 给4M做U盘
0 p4 _3 g9 i4 W% e; A5 N - Mass_Block_Size[0] =512; //设置SPI FLASH的操作扇区大小为512
E; E4 Z' v* M8 ~; @ W# a) E - Mass_Block_Count[0]=Mass_Memory_Size[0]/Mass_Block_Size[0];
3 X! [ |9 _* q4 g, N0 D- B - 3 ]& n5 v1 u( R& a0 Y
- USB_MSC_Configuration();
复制代码 G/ y- \. A5 v# b E' b% R
$ { O/ `2 a, r+ _% f0 L: Z( }: v7 g' [" H* ]& I) J2 X# u
编译下载后就可以正常运行了。
! c7 [) X2 f4 ^3 ]; I; x( ~( W$ Y0 }2 X7 X; a8 X
( |: Y x# H5 Q" B B6 x8 D. t
. O1 y- B5 f( @! m$ N: z传输文件、新建文件夹都可以了,就是传输速度感人。
}! r( F& j) X9 a% s
: W0 F7 V% }) |3 { D7 ]以上就是USB虚拟U盘的所有内容了,内容太多而且复杂,所以如果真的想亲手试试,还是要点耐心的。虽然能学到的并不是很多,这里主要可以干茶mass_mal部分,可以学到不少东西,方便以后的熟练运用。我这里也是为以后更进一步的开发做铺垫。6 g _+ v6 {, C- c
$ ]/ L! J# i5 l4 {% M( _ T9 _$ W
% C$ y% M$ p3 V1 R) C) g3 V# P7 D$ ~2 Q. `3 A+ M) m
|