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