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