|
1.SPI总线及W25QXX芯片 1.1 SPI总线简介 SPI全称Serial Peripheral Interface,即串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在EEPROM、FLASH、实时时钟、AD转换器,还有数字信号处理器和数字信号解码器之间。SPI是一种高速的、全双工、同步通讯总线,在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局节省空间提供了方便,正是这种简单易用的特性,如今越来越多的芯片集成了这种通讯协议。下图是SPI内部结构简易图 - s/ d2 ?0 [/ `! H6 W3 V" F
/ K+ Y# y0 E+ K从上图可以看出,主设备和从设备都有一个串行移位寄存器,主设备通过向它的SPI串行寄存器写入一个字节来发起一次传输,寄存器通过MOSI信号线将字节传送给从设备,从设备也将自已的移位寄存器中的内容通过MISO信号线返回给主设备。这样两个移位寄存器中的内容就被交换。外设的写操作和读操作时同步完成的,如果只进行写操作,主设备只需要忽略接收到的字节,如果主设备要进行读操作,就必须发送一个空字节来引发从设备的传输。 SPI接口一般使用4条线通讯,单向传输时也可以使用3条线,其中3条线为SPI总线(MISO,MOSI,SCLK),1条为SPI片选信号线(CS),它们的作用如下:
5 t4 I2 i" {. {, v- AMISO:主设备数据输入,从设备数据输出 MOSI:主设备数据输出,从设备数据输入 SCLK:时钟信号,由主设备产生 + r! x% ~+ R& [9 W2 B+ ]+ P
CS:从设备片选信号,由主设备控制 . o' U* d$ q6 I( l
' p) l- J# X+ e: }8 q9 s( e
SPI使用MOSI/MISO信号线来传输数据,使用SCLK信号线进行数据同步。MOSI/MISO数据线在SCLK的每个时钟周期传输1位数据,且数据输入输出是同时进行的。数据传输时,MSB先行或LSB先行没有硬性规定,但是两个SPI通讯设备之间必须使用同样的协定,一般都会采用MSB先行模式。
! ?8 E7 P/ p! ?7 f8 {4 V; ^& U当有多个SPI从设备与SPI主设备相连时,设备的MOSI/MISO/SCLK信号线并联到相同的SPI总线上,即无论有多少个从设备,都共同使用者3条总线;而每个从设备都有独立的1条CS信号线,该信号线独占主设备的一个引脚,即有多少个从设备就有多少条片选信号线。当主设备要选择从设备时,把该从设备的CS信号线设置为低电平,该从设备即被选中(片选有效),接着主设备开始与从设备进行SPI通讯。& P4 i1 r: l+ u( H; r F! E
SPI总线根据时钟极性(CPOL)和时钟相位(CPHA)的配置不同,可以有四种工作方式: 5 t" ^! c. Y" ]# \1 G2 t7 @' V
CPOL=0:串行同步时钟的空闲状态为低电平 CPOL=1:串行同步时钟的空闲状态为高电平 CPHA=0:在串行同步时钟的第一个跳变沿(上升或下降)数据被采样
) S/ b$ r3 k- q: S- BCPHA=1:在串行同步时钟的第二个跳变沿(上升或下降)数据被采样 6 o' b/ p R* M9 h7 Y
8 k6 v( ], u) ^( M$ Z
4 _5 t2 F- q. m, }1 j! [. d& U
1.2 W25QXX芯片简介 W25QXX芯片是华邦公司推出的大容量SPI FLASH产品,该系列有W25Q16/32/62/128等。本例程使用W25Q64,W25Q64容量为64Mbits(8M字节):8MB的容量分为128个块(Block)(块大小为64KB),每个块又分为16个扇区(Sector)(扇区大小为4KB);W25Q64的最小擦除单位为一个扇区即4KB,因此在选择芯片的时候必须要有4K以上的SRAM(可以开辟4K的缓冲区)。W25Q64的擦写周期多达10万次,具有20年的数据保存期限。下表是W25QXX的常用命令表
! W; P* Y( B; t, h ?+ a5 Y4 i$ u6 z2 D% I, r ]( z( w9 l
2.硬件设计 D1指示灯用来提示系统运行状态,K_UP按键用来控制W25Q64数据写入,K_DOWN按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息
; D* f" N- {/ k- d% P4 m指示灯D1 USART1串口 W25Q64 ?0 R8 H/ l) M# `
K_UP和K_DOWN按键/ {) @+ u% Z; ]2 H5 F% W2 l% ]: q
+ n- Q% Y, [: j8 W3 w& K. i
7 Q, f& [& S" b" i$ e- Z 1 s2 U; g" ^" {$ F, W7 n
9 Y9 o# f, @2 M' _; Q& x K; c) Q9 ~
3.软件设计  3.1 STM32CubeMX设置 ➡️ RCC设置外接HSE,时钟设置为72M ➡️ PC0设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平 ➡️ USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位 ➡️ PA0设置为GPIO输入模式、下拉模式;PE3设置为GPIO输入模式、上拉模式 ➡️ PG13设置为GPIO推挽输出模式、上拉、高速(片选引脚) ➡️ 激活SPI2,不开启NSS,数据长度8位,MSB先输出,分频因子256,CPOL为HIGH,CPHA为第二个边沿,不开启CRC检验,NSS为软件控制 ; G# g6 g5 @! B8 P# w4 B
. ~" ^$ L$ C1 k2 q6 a➡️输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
- H+ F& J" O, |) d) ]& W
3.2 MDK-ARM软件编程 ➡️ 在spi.c文件下可以看到SPI2的初始化函数,片选管脚的初始化在gpio.c中
6 S% H9 E" r |3 m" U# e
- void MX_SPI2_Init(void){+ {% o' n' r+ [
- hspi2.Instance = SPI2;
% H# L+ {1 j, b; `: w# f) q7 N - hspi2.Init.Mode = SPI_MODE_MASTER;//设置为主模式
1 g, V; |& t) u - hspi2.Init.Direction = SPI_DIRECTION_2LINES;//双线模式- s& k4 ?6 c# W. v; R+ D
- hspi2.Init.DataSize = SPI_DATASIZE_8BIT;//8位数据长度2 k, M1 [, i' K U
- hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;//串行同步时钟空闲状态为高电平' }, P; j' i$ A) _5 h+ I
- hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;//第二个跳变沿采样
4 L3 d6 p1 I* N - hspi2.Init.NSS = SPI_NSS_SOFT;//NSS软件控制
4 G" M6 E0 U; z - hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;//分配因子256
$ z& t4 A- J9 x* j' m' L1 R# q - hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSB先行
0 {; |0 R2 c% n. r8 H; r - hspi2.Init.TIMode = SPI_TIMODE_DISABLE;//关闭TI模式
N9 I$ C$ M( J9 p - hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭硬件CRC校验
( E# }& X8 |/ J+ ]/ f3 {3 }5 b - hspi2.Init.CRCPolynomial = 10;' g }. _/ w( P9 h5 ?
- if (HAL_SPI_Init(&hspi2) != HAL_OK){
6 Z3 ^1 L0 x4 d6 z2 _. a - Error_Handler();
1 {4 j; L9 B! S' @ - }
- O" m6 c9 x: N/ p3 e/ L - }
; Y8 W" @8 f! I5 K8 ^* i2 x7 s - & Y& p) D7 o$ I B+ ]
- void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){8 t$ H) K- T0 ]* U# [) h
- GPIO_InitTypeDef GPIO_InitStruct = {0};
: L9 l: x. `4 U - if(spiHandle->Instance==SPI2){8 v4 N4 X; }, p/ }* W) C% w
- __HAL_RCC_SPI2_CLK_ENABLE(); 2 `- P& x+ c% y4 i
- __HAL_RCC_GPIOB_CLK_ENABLE();. f! ]8 e! O% j* v# A3 m2 Z
- /**SPI2 GPIO Configuration
, c. K$ J- s) |$ B& M8 ?$ X1 h& _) ^$ K - PB13 ------> SPI2_SCK
3 L5 r& N t5 P( t9 P: [2 N - PB14 ------> SPI2_MISO
. o" [2 F& r) Y( G: _+ h - PB15 ------> SPI2_MOSI */
& D" Z7 r2 u2 o0 u) A - GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;
$ u$ h6 X6 f7 A3 s# x7 R$ v8 @# f M - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
$ f E: O! A+ r - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
' _( |. s1 v7 @1 n* y5 e - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
" U# P& n7 k' ?) v: t$ V - GPIO_InitStruct.Pin = GPIO_PIN_14;
4 A# y9 c2 p2 Z - GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
5 G O& k" n" H, S - GPIO_InitStruct.Pull = GPIO_NOPULL;
: U5 R, z; _% A; Y, c3 [/ @3 a& v% ] - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
% A0 t+ I% S. i0 j - }5 i Z3 h1 Q: e2 C c
- }
复制代码 * Q( o* j$ f/ r2 e1 c. q' z, a2 f
➡️ 创建按键驱动文件key.c 和相关头文件key.h ➡️ 创建包含W25Q64芯片的相关操作函数及驱动函数的文件w25qxx.c和w25qxx.h,这里仅介绍几个重要的函数,源文件下载方式见文末介绍 - //这里仅介绍几个重要的函数- M! K2 h, s! Z# z! u" ~& c$ b
- void W25QXX_Init(void){4 i3 c9 |$ G7 d9 D( x; |, `
- W25Qx_Disable();
' _# K6 l+ L0 P, A9 C0 m - W25QXX_TYPE = W25QXX_ReadID();//读取芯片ID( f! M9 S/ O8 ^/ h: c- S
- printf("FLASH ID:%X\r\n",W25QXX_TYPE);
z3 s0 ?* Q3 e0 k7 k2 S. I6 G - if(W25QXX_TYPE == 0xc816)- ?; d9 A) U4 E
- printf("FLASH TYPE:W25Q64\r\n");
- s: ~, \8 D. B - }
6 x+ b/ c5 Z: x$ v7 l( r% K - ( \7 X4 v$ w! f7 n8 {5 E
- uint16_t W25QXX_ReadID(void){0 u; t# X% l; n' J
- uint16_t ID;
8 t. A- U3 K3 M- a" Z9 @+ S& x8 H - uint8_t id[2]={0};
1 i. {6 v- D+ S+ n/ X. U+ R - uint8_t cmd[4] = {W25X_ManufactDeviceID,0x00,0x00,0x00};//读取ID命令 : W( V$ U9 d0 p- P+ N0 Y7 f
- W25Qx_Enable();//使能器件+ ?3 V# m5 @( Y' j; X( g: q) I
- HAL_SPI_Transmit(&hspi2,cmd,4,1000);0 k& c/ @, s. n: E3 Z8 ?& [4 o* `9 M
- HAL_SPI_Receive(&hspi2,id,2,1000);0 P8 y* C& W; m/ x3 M1 q
- W25Qx_Disable();//取消片选 ; ^, k+ B7 p* L* U e
- ID = (((uint16_t)id[0])<<8)|id[1];0 _8 v9 Z6 n, s" Q* x7 F+ @
- return ID;
1 |% J3 P8 A! r8 V5 q2 P - }
7 J' e+ N1 T0 E; N6 q - 7 }* B# C# ^/ E s! p
- void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead){
6 ]! ~* O& x7 ] - uint8_t cmd[4] = {0};
# m5 V$ u: {/ E6 t5 l; ? - cmd[0] = W25X_ReadData;//读取命令" F9 F, V G, j' C: y/ T: Q; b" q
- cmd[1] = ((uint8_t)(ReadAddr>>16));
! _" V. c& {# [+ T - cmd[2] = ((uint8_t)(ReadAddr>>8));
" x1 H" f2 g. R2 i! C0 Q7 p* } - cmd[3] = ((uint8_t)ReadAddr);
# k3 T. @' B: D6 |: M* @$ H4 g - [1 t. G, K0 r$ P% _
- W25Qx_Enable();//使能器件
& ]. |2 D- [# o0 B/ W# `# p - HAL_SPI_Transmit(&hspi2,cmd,4,1000);5 j% ]- l3 z7 L6 l0 p! H
- HAL_SPI_Receive(&hspi2,pBuffer,NumByteToRead,1000);
. L6 ~" N* h' q6 W5 [) k - W25Qx_Disable();//取消片选 ; t$ I/ u; k1 C5 [: C* O) s4 E
- }7 U7 T5 j- ?1 j" m6 ~6 G( n
- ! A/ g. q c5 ^: B+ v* e/ x
- void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite){
, {* g9 S, v3 z - uint32_t secpos;! O0 B3 c- p7 s" F8 X3 E$ q+ h
- uint16_t secoff;% T! a) d9 l! T6 x5 }4 B, o% m8 ?
- uint16_t secremain;- O: E) C0 S) l: d! e6 ^4 j1 B
- uint16_t i;
% T7 | r/ I. \) w: V8 T- u - uint8_t *W25QXX_BUF;
" o# e/ j6 b9 f, [$ b) _# G - - w/ R" X( t$ `
- W25QXX_BUF = W25QXX_BUFFER;
) O9 E2 q& D$ h, G9 ]. n& M: M - secpos = WriteAddr/4096; //扇区地址
) Y& h/ N: K- c, z - secpos = WriteAddr%4096; //在扇区里的偏移+ ]$ R; v$ k: v8 G3 k8 k$ z- o+ w
- secremain = 4096-secoff; //扇区剩余空间大小
! \% _+ `! N+ ^" Z; z" W - printf("WriteAddr:0x%X,NumByteToWrite:%d\r\n",WriteAddr,NumByteToWrite);1 h) ?6 J# ~% D. E
- if(NumByteToWrite <= secremain) //不大于4K字节# w) O6 E9 ]3 {' v* r3 o) r6 j
- secremain = NumByteToWrite;4 |" `5 {; H; {$ E* W% k3 E5 I
- while(1){: M$ |5 ]+ F- D( L+ I
- W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读取整个扇区内容7 K4 ]4 z7 h5 H2 Z
- for(i=0;i<secremain;i++){//校验数据2 p' R, m& o+ X; M; O
- if(W25QXX_BUF[secoff+i] != 0xff)//需要擦除5 R" ]5 h! I7 E g
- break;
: \8 C% r* o, E& H' x) y0 t - }
- d% a# n% Z* B% z1 T -
0 ^" Z6 I9 Z9 E9 U; T2 U - if(i < secremain){//需要擦除( w6 K9 g; ]+ ~' ^3 F2 i
- W25QXX_Erase_Sector(secpos);//擦除扇区
6 b2 t2 h: d& O) p0 P+ j, z7 Z/ s( E - for(i=0;i<secremain;i++){
9 ]& D% B8 v! G' d: ]2 n - W25QXX_BUF[i+secoff] = pBuffer[i];
5 u9 q9 K9 Q$ \) e" w - }, G9 _4 U3 b. F+ E, Y- ^
- W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区
4 q: }/ E$ F! C) o8 T6 S* D* T4 X - }! Y' r) g, J& v0 L4 u; ~
- else{7 v+ X9 P- T6 b; ]
- W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写入扇区剩余空间
' R" b" a- I% T7 l% b& X# m - }
& x5 Q( J3 e0 y: U - ' A6 j7 r3 @' ^" |6 q( ?5 V
- if(NumByteToWrite == secremain){//写入结束了% | W$ a1 b. D+ X& D2 E
- break;
# s1 e. ]$ H: Z |9 C" K$ H7 T& ^ - }5 h+ X/ D- |( g" n
- else{ //写入未结束
5 _' }+ ^& P) W$ j8 q - secpos++; //扇区地址增18 ^7 l( d9 E' Z/ K
- secoff = 0; //偏移位置为0 ! W( W7 m3 `0 O3 [, c
- pBuffer += secremain; //指针偏移9 l7 b6 N4 u* Q' g
- WriteAddr += secremain; //写地址偏移
8 C6 l- ]' ~% p- L. c - NumByteToWrite -= secremain;//字节数递减3 \8 l5 ?4 K4 a- h7 R% q. H
- if(NumByteToWrite > 4096) % l: R; s- V5 b# N; a4 a4 s
- secremain = 4096; //下个扇区还没是写不完# q4 k: m; z0 C$ b: Q; w* R
- else# j* x5 I# P, p2 o y7 C7 h5 F2 I
- secremain = NumByteToWrite;//下个扇区可以写完了. K5 u+ W0 D$ S
- } c M7 Y. m k4 d: E* \
- }7 ?6 n. c; Q0 Y& G3 r% [4 Y
- }
复制代码 ; C' \8 ]- \8 h4 H7 r3 G9 X7 a
➡️ 在main.c文件下编写SPI测试代码 - /* USER CODE BEGIN PV */" d9 ~/ R# x: R: g+ k% S
- uint8_t wData[0x100];. D* V0 z, B' O9 ?2 U6 O
- uint8_t rData[0x100];/ h# n: y4 }! c7 C% g2 T
- uint32_t i;
* a/ A. k- E" c$ C3 N - /* USER CODE END PV */# E7 I( U' m2 l' t: T% u; o, j
- int main(void){' P4 Q1 ^# W, A. L9 }# B
- /* USER CODE BEGIN 1 */
2 |7 z) U0 z4 ?7 ?/ h. R - uint8_t key;9 C) T, s4 Q4 n. l5 k6 {
- /* USER CODE END 1 */) g; s) d. m' D6 s0 v- H
- HAL_Init();5 h9 P s2 e; V. w
- SystemClock_Config();
F6 l* E. ~, u# l8 s - MX_GPIO_Init();$ T. z) A2 G; S- a0 a2 ]
- MX_SPI2_Init();9 l# m0 ?) g7 h4 B
- MX_USART1_UART_Init();
$ W+ X) Y3 x2 N9 w - /* USER CODE BEGIN 2 */* d+ A: o2 h( S: O3 R6 h) M6 w& w
- W25QXX_Init();
' J5 n- } X& R Y, Y6 P0 g; W - for(i=0;i<0x100;i++){
1 w$ X6 A/ v' F% }( E' K- y - wData[i] = i;
$ ^2 `- A7 s) g2 J5 B( m2 Z. W+ } - rData[i] = 0;+ p3 p& `+ o! R# p U
- } ; ~5 y# k: p' a! i8 l9 e/ b
- /* USER CODE END 2 */
: A x1 C6 j; {6 I - while (1){' T' H! p# x1 Q3 j
- key = KEY_Scan(0);
. n9 p4 d+ a' s4 S - if(key == KEY_UP_PRES){1 ^' g! m& u" m' `# y% Y
- printf("KEY_UP_PRES write data...\r\n");3 F1 ~; f; ^" ^/ R% E
- W25QXX_Erase_Sector(0);3 q- H: N( }8 Z- Q$ W4 d
- W25QXX_Write(wData,0,256);
( u5 v) T* ]- X2 M - }
+ C' @" l; o% p7 d. \% o -
' `4 A* L' ]! g) E1 t' B - if(key == KEY_DOWN_PRES){/ Y% I3 c! c% B4 l5 a
- printf("KEY_DOWN_PRES read data...\r\n");
0 f) Y0 R3 C7 v" n - W25QXX_Read(rData,0,256);5 Z- `# n# p( ^/ E) e
- for(i=0;i<256;i++){
6 ]5 P3 X1 @! e7 Q7 G' v; [' z - printf("0x%02X ",rData[i]);) E8 ~! ~' y7 Z9 x' F
- }
1 Z/ [* m( i% L; z8 t - }
" t7 m6 I T, a - # }' {7 m) R1 b& o
- HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
+ P; z# Z! X3 B! B - HAL_Delay(200);
$ N& P5 }2 Y! Y4 W7 y6 F9 Y, N! \ - }( @7 X6 [: a9 @3 X
- }
复制代码
3 `! {1 h A/ o; R0 f4.下载验证 编译无误下载到开发板后,可以看到D1指示灯不断闪烁,当按下K_UP按键后数据写入到W25Q64芯片内,当按下K_DOWN按键后读取W25Q64芯片的值,同时串口打印出相应信息 * |: _. P" ^ b' _
5 ~4 y2 X0 M# a3 y9 ~' {: z8 Y
( _/ L- K: x' F! U1 D# A
转载自: 嵌入式攻城狮# ], U* ?: q" r% f' W* E
如有侵权请联系删除. m# _3 x# R" E1 g, O1 M
$ V* a5 o. f+ A
t0 W4 W6 u: S' f& g+ U/ h8 z0 r
- [9 t N t, S4 p9 l. t' X' |6 n |