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