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