|
1.SPI总线及W25QXX芯片 1.1 SPI总线简介 SPI全称Serial Peripheral Interface,即串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在EEPROM、FLASH、实时时钟、AD转换器,还有数字信号处理器和数字信号解码器之间。SPI是一种高速的、全双工、同步通讯总线,在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局节省空间提供了方便,正是这种简单易用的特性,如今越来越多的芯片集成了这种通讯协议。下图是SPI内部结构简易图 0 f7 f, p7 w3 ^7 k" i. }
; v" P, Z1 @+ p6 S2 ]从上图可以看出,主设备和从设备都有一个串行移位寄存器,主设备通过向它的SPI串行寄存器写入一个字节来发起一次传输,寄存器通过MOSI信号线将字节传送给从设备,从设备也将自已的移位寄存器中的内容通过MISO信号线返回给主设备。这样两个移位寄存器中的内容就被交换。外设的写操作和读操作时同步完成的,如果只进行写操作,主设备只需要忽略接收到的字节,如果主设备要进行读操作,就必须发送一个空字节来引发从设备的传输。 SPI接口一般使用4条线通讯,单向传输时也可以使用3条线,其中3条线为SPI总线(MISO,MOSI,SCLK),1条为SPI片选信号线(CS),它们的作用如下:
9 z4 Y9 x& _# Y- MMISO:主设备数据输入,从设备数据输出 MOSI:主设备数据输出,从设备数据输入 SCLK:时钟信号,由主设备产生
4 z3 s- s7 a+ _$ m5 D0 e" N$ {: V6 sCS:从设备片选信号,由主设备控制
9 P4 v, x/ Y( d- O* v! g# Z" J# b" k1 V
SPI使用MOSI/MISO信号线来传输数据,使用SCLK信号线进行数据同步。MOSI/MISO数据线在SCLK的每个时钟周期传输1位数据,且数据输入输出是同时进行的。数据传输时,MSB先行或LSB先行没有硬性规定,但是两个SPI通讯设备之间必须使用同样的协定,一般都会采用MSB先行模式。
4 a: l0 ^- P# S% X当有多个SPI从设备与SPI主设备相连时,设备的MOSI/MISO/SCLK信号线并联到相同的SPI总线上,即无论有多少个从设备,都共同使用者3条总线;而每个从设备都有独立的1条CS信号线,该信号线独占主设备的一个引脚,即有多少个从设备就有多少条片选信号线。当主设备要选择从设备时,把该从设备的CS信号线设置为低电平,该从设备即被选中(片选有效),接着主设备开始与从设备进行SPI通讯。
% E/ @( c9 O! Z5 ?# `SPI总线根据时钟极性(CPOL)和时钟相位(CPHA)的配置不同,可以有四种工作方式:
/ e! r1 ?7 x* d7 QCPOL=0:串行同步时钟的空闲状态为低电平 CPOL=1:串行同步时钟的空闲状态为高电平 CPHA=0:在串行同步时钟的第一个跳变沿(上升或下降)数据被采样 ) o& A. x0 P6 H$ Y) u3 o0 L
CPHA=1:在串行同步时钟的第二个跳变沿(上升或下降)数据被采样 _4 U6 L. c: r+ d+ Z5 Z
- e8 P8 {9 I5 v$ t* m# G
( }; M0 K! w: x1 c4 \' @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的常用命令表 , f: F3 \5 b# S' N
* }) p) ~1 A, F1 J) v1 X
2.硬件设计 D1指示灯用来提示系统运行状态,K_UP按键用来控制W25Q64数据写入,K_DOWN按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息 : n2 ?. q) c$ `+ A( j9 Z3 [/ A5 ?+ S2 K
指示灯D1 USART1串口 W25Q64 $ H l3 Z9 ~/ R- j3 M3 x, q
K_UP和K_DOWN按键
* p3 c) ~1 E! u+ I) ]! a ; g5 ~4 U9 ~6 e
. N& N4 D: p! q( Q6 i : P% d7 @3 {8 w3 F
8 a# w0 \; M a1 P
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为软件控制 W0 s- |) E8 J) u
. l& a9 E; j) p+ V7 D+ l) G➡️输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码 ) ]4 M/ k: ^; Z3 p# b: R
3.2 MDK-ARM软件编程 ➡️ 在spi.c文件下可以看到SPI2的初始化函数,片选管脚的初始化在gpio.c中 3 c* {5 i" A2 u$ A
- void MX_SPI2_Init(void){" {( Z( L9 I3 T4 }) ]
- hspi2.Instance = SPI2;
: \6 y V% P& M% t5 i - hspi2.Init.Mode = SPI_MODE_MASTER;//设置为主模式. x6 @! a8 C4 P9 {# m
- hspi2.Init.Direction = SPI_DIRECTION_2LINES;//双线模式
* n3 Z5 a. O3 L- l2 K - hspi2.Init.DataSize = SPI_DATASIZE_8BIT;//8位数据长度
& H7 k3 `5 U$ n5 Z6 L - hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;//串行同步时钟空闲状态为高电平) }2 L1 @: q a3 h+ v# y. \6 W
- hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;//第二个跳变沿采样
5 z9 d6 F3 k8 v' o/ x5 X0 g3 j' b - hspi2.Init.NSS = SPI_NSS_SOFT;//NSS软件控制( z; b. s4 p9 O0 z& U' ~
- hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;//分配因子256
7 O5 i" U( F1 @) }9 h* y' I - hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSB先行: R+ C- ]) n; `- I8 O& u" A3 m
- hspi2.Init.TIMode = SPI_TIMODE_DISABLE;//关闭TI模式
" L5 H/ J6 G+ s) q - hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭硬件CRC校验" a- h7 F) b. y
- hspi2.Init.CRCPolynomial = 10;
t2 N9 P4 q4 E" r) s6 H - if (HAL_SPI_Init(&hspi2) != HAL_OK){9 n8 e, F& F# J
- Error_Handler();
5 v" C2 _9 U6 i- W+ \' |6 ? - }: z, e* F; I, m) R. j
- }
# w1 A8 h4 y8 j - . _/ ~( r" A m1 Q
- void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){* w* Y0 |- F8 I/ Z
- GPIO_InitTypeDef GPIO_InitStruct = {0};, O* Q% C( G7 W! }
- if(spiHandle->Instance==SPI2){
* T' i: p* g/ m0 P% M% ~6 [ - __HAL_RCC_SPI2_CLK_ENABLE();
4 j# M$ @3 L4 p' j9 K. ? - __HAL_RCC_GPIOB_CLK_ENABLE();. o( ~/ i& {- Z$ X: Z& `
- /**SPI2 GPIO Configuration
8 ]; k- g" r7 ]! z - PB13 ------> SPI2_SCK, g: v) b0 V( w, y; l$ {# ^
- PB14 ------> SPI2_MISO
) i2 _' w9 ^5 f) Y) o. q - PB15 ------> SPI2_MOSI */. d9 m1 F% l3 W4 C* P: H) a% k
- GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;
8 \' v9 s1 a- N B1 ^+ ` - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
/ W, I2 p2 h7 j5 B8 l1 x - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
+ Y/ _/ T+ \ r+ U: e - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
; d! e& I! A# o - GPIO_InitStruct.Pin = GPIO_PIN_14;. L: d- N5 W: U- D2 J$ t
- GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
1 J) w5 ^1 b" J& z5 h+ z! e - GPIO_InitStruct.Pull = GPIO_NOPULL;
$ U& @; s5 s# G - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);8 u8 Z6 G R/ U2 Z
- }
0 z" t/ T* E7 ` - }
复制代码
2 g l' K0 Z$ b➡️ 创建按键驱动文件key.c 和相关头文件key.h ➡️ 创建包含W25Q64芯片的相关操作函数及驱动函数的文件w25qxx.c和w25qxx.h,这里仅介绍几个重要的函数,源文件下载方式见文末介绍 - //这里仅介绍几个重要的函数- J9 E8 z2 p4 z% B3 h. L0 j0 [
- void W25QXX_Init(void){
- M: p/ R0 J: ~2 W0 i2 D - W25Qx_Disable();
' A7 g1 e/ J ] z% p' M - W25QXX_TYPE = W25QXX_ReadID();//读取芯片ID9 r) b8 B2 X& f0 {& `5 j3 v9 a
- printf("FLASH ID:%X\r\n",W25QXX_TYPE);3 i) J. c3 h, U7 P8 C4 o( h
- if(W25QXX_TYPE == 0xc816)
$ }" _7 ^2 w- [" f" y! {. _8 V% t - printf("FLASH TYPE:W25Q64\r\n");
- I6 g9 W# j& }( L - }
5 {2 Q+ I$ n3 b, r9 b
2 ]0 ~0 K- F+ U: i g- uint16_t W25QXX_ReadID(void){! T( H7 \' r$ Z) n+ X
- uint16_t ID;& n% x" l, f+ B4 p! F W# A
- uint8_t id[2]={0};
8 z, M. U9 K2 L6 a% H1 t1 N - uint8_t cmd[4] = {W25X_ManufactDeviceID,0x00,0x00,0x00};//读取ID命令
" I; h# n3 h$ `; v - W25Qx_Enable();//使能器件2 V# w) @/ R+ K4 T* e1 E2 T5 g
- HAL_SPI_Transmit(&hspi2,cmd,4,1000);% L# r* }% a9 c7 @8 C
- HAL_SPI_Receive(&hspi2,id,2,1000);( |: }" l9 y% W
- W25Qx_Disable();//取消片选
" K6 T/ F3 a1 [5 c2 Z) L - ID = (((uint16_t)id[0])<<8)|id[1];7 z `+ j" ?: R i" ?7 `
- return ID;
! U# k+ Q) h7 _6 F! Y - }
: `& {/ [% z* T$ {$ s. | X - * x7 \' p! R; F- r) j8 G
- void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead){+ ]# H/ O @! w
- uint8_t cmd[4] = {0};) m$ Z5 G1 K0 J4 U
- cmd[0] = W25X_ReadData;//读取命令
7 k3 \9 \7 u8 O7 i3 S& M! H: {9 U - cmd[1] = ((uint8_t)(ReadAddr>>16));
- W( K6 O" j4 |: S5 q - cmd[2] = ((uint8_t)(ReadAddr>>8));$ W m. x- p9 M! X2 R
- cmd[3] = ((uint8_t)ReadAddr);
2 H @; q7 \! v6 C ?4 N
S5 F: ^/ c5 u( D9 d( ^3 p- W25Qx_Enable();//使能器件+ J+ _7 i% k" F1 z& e: d& v
- HAL_SPI_Transmit(&hspi2,cmd,4,1000);
6 d$ [" n' V+ `1 W7 L8 @8 k - HAL_SPI_Receive(&hspi2,pBuffer,NumByteToRead,1000);
6 x+ j3 D& k1 h+ E, X( @ - W25Qx_Disable();//取消片选 * [5 ?! Z: F; V" S
- }
2 I* v- v, [( e( R8 Y3 r
0 b+ F$ M( H8 o, o( B3 O1 P ^- void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite){
0 g3 R! o9 l- z2 z% u - uint32_t secpos;
% R& I% M- s( { - uint16_t secoff;4 p$ q* d* C2 b( W
- uint16_t secremain;( @; D6 w! O7 r: ~
- uint16_t i;2 H( L- G, D+ [1 M; O
- uint8_t *W25QXX_BUF;
7 c0 h' b5 `# U$ v% y3 B -
5 S$ v. A) y1 I5 ]4 X - W25QXX_BUF = W25QXX_BUFFER;
) J$ c# J; N1 ` - secpos = WriteAddr/4096; //扇区地址% w# Y6 T! x4 G; o$ [+ n
- secpos = WriteAddr%4096; //在扇区里的偏移% E3 m: @ }! a# S) t
- secremain = 4096-secoff; //扇区剩余空间大小
9 p: L( \# @& C - printf("WriteAddr:0x%X,NumByteToWrite:%d\r\n",WriteAddr,NumByteToWrite);$ f2 p# ?: h0 f
- if(NumByteToWrite <= secremain) //不大于4K字节4 [4 z) b5 J; |! L R+ H& K
- secremain = NumByteToWrite;) d1 C: U! W* Y
- while(1){
3 P7 f) @. `. s - W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读取整个扇区内容
/ D1 Q6 C4 h4 } - for(i=0;i<secremain;i++){//校验数据; u+ R* q3 |7 g3 B
- if(W25QXX_BUF[secoff+i] != 0xff)//需要擦除
! ]) @2 w0 r* m7 I$ y% u - break;
+ w. G: h- B1 T x5 d) E' r+ T - }$ W6 x- a! o) c8 A, [- F
- & k# F1 g( _8 [9 r$ z0 N
- if(i < secremain){//需要擦除
7 e* F3 e8 G: c' |. Q, i: ? - W25QXX_Erase_Sector(secpos);//擦除扇区
4 U+ v! E7 D A# D- z! I, B - for(i=0;i<secremain;i++){
1 m. l3 v3 ]) a- d+ k7 V; @ - W25QXX_BUF[i+secoff] = pBuffer[i];
/ ~2 A$ F: }- g9 @( W$ s8 B - }3 n4 E/ y. p9 q0 I/ K
- W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区2 e7 r) F- M4 G/ k9 K, t& z8 H
- }) _. m% n4 t% {/ l8 S2 |) B
- else{
' h; F* y L6 P# u& o% G# c6 l" R - W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写入扇区剩余空间
/ h6 C1 _. y$ S2 b - }/ n* V; H+ K+ {; |$ A1 s1 v
-
# M4 f, o$ z x p' t - if(NumByteToWrite == secremain){//写入结束了# L1 E' s' h' s @+ x- P9 f
- break;
5 h% X- p4 M/ k - }- i3 B8 f; G/ L( i/ V
- else{ //写入未结束
5 x2 f# S: y5 M2 K4 z - secpos++; //扇区地址增1
3 j6 N" H( i/ H - secoff = 0; //偏移位置为0 1 @/ h; m, b2 F3 K ~3 a
- pBuffer += secremain; //指针偏移# ^! ?$ K. H; f0 c9 L) q# `$ f
- WriteAddr += secremain; //写地址偏移8 X8 p# ]+ c9 Q$ F( C0 L8 H
- NumByteToWrite -= secremain;//字节数递减! n: V2 R! t4 I
- if(NumByteToWrite > 4096) 1 a- r8 t/ O' ?
- secremain = 4096; //下个扇区还没是写不完8 v" K- S( S: x: @. u0 {9 ^3 n
- else
% h& R3 i# b9 n, P- K$ G - secremain = NumByteToWrite;//下个扇区可以写完了
+ h4 C1 ~* j8 b! h1 m+ A - }' Y; E8 Z8 J# \1 h0 t2 Q9 x6 S
- }
* z6 p: ?" w3 N - }
复制代码 5 z: H; S6 F5 W, v' x8 R, {
➡️ 在main.c文件下编写SPI测试代码 - /* USER CODE BEGIN PV */' {, j L& Y% N8 K% o
- uint8_t wData[0x100];
/ e2 k" H- [' k! H) { - uint8_t rData[0x100];3 G: P4 i) {% c+ n. f, ~
- uint32_t i;
, \' k4 F* O- Z& O - /* USER CODE END PV */% O! n, V# X5 P6 U: A
- int main(void){% ]$ d/ v& k5 z/ Z$ q# f, K. _
- /* USER CODE BEGIN 1 */7 L: m/ z- {; Y
- uint8_t key;6 ]# W* B6 @( N) e; U
- /* USER CODE END 1 */
- P2 [" P7 U0 ~. I - HAL_Init();
4 a5 g( e2 A3 x2 x- b1 L9 r: [ - SystemClock_Config();9 m) X6 D8 `1 k; n [" v6 G
- MX_GPIO_Init();- `) t: f" J: q8 n3 e6 B' v. V
- MX_SPI2_Init();1 ], r( j/ ^0 h& Q* j# k1 \7 l" `
- MX_USART1_UART_Init();9 R" P! K/ _( ^: ]0 S# x4 A8 j1 C
- /* USER CODE BEGIN 2 */
a& R! v% L5 v$ h - W25QXX_Init();1 s3 N" e* ?& F" u% l
- for(i=0;i<0x100;i++){& Y; D% o' O$ o8 \0 u* Q
- wData[i] = i;) l7 s; v+ u8 I) k' P, Q$ b
- rData[i] = 0;
$ J6 @' C6 `: f. u; \6 W5 K - } + o6 D ^ Q3 g5 Q% {
- /* USER CODE END 2 */
6 F& j( b0 K% t - while (1){
8 \4 R$ z% r8 ~& | - key = KEY_Scan(0);* S8 v3 @' G. A! Q
- if(key == KEY_UP_PRES){
8 l% ]+ [# }" k: g" x9 j0 u% m! E9 x - printf("KEY_UP_PRES write data...\r\n");
' \. T! `5 G, s2 h" f4 O+ Q m - W25QXX_Erase_Sector(0);
$ ]- s1 a A3 z; T0 h! B/ b - W25QXX_Write(wData,0,256);
$ p7 j1 Z* C- e( a' c% } - }
- z1 Y* n$ h9 n6 @ -
/ R: B5 m4 F. n+ D! M - if(key == KEY_DOWN_PRES){
# p \1 S! h9 a8 N# l - printf("KEY_DOWN_PRES read data...\r\n");% O, v8 l; W+ r) `) l9 r3 ?
- W25QXX_Read(rData,0,256);& Y) u0 u; J9 D* f
- for(i=0;i<256;i++){
, ~% L, v1 ~5 Z3 m) h2 ~4 C! ^ - printf("0x%02X ",rData[i]);
+ B) @! E1 H' a) c, N! @; m: B( H - }
/ Q" ~% v0 q0 h& @2 V9 m - }
. M- ^# g! v2 l" l5 e - & U# t8 u# t, r+ N% i
- HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);. V5 a7 L' v. \6 {% f
- HAL_Delay(200);8 c g; t4 n* U0 W0 ?
- }+ z+ m4 N: e1 V3 _( Q" X
- }
复制代码 # k5 k ~% a0 R- G4 {4 \+ c$ l
4.下载验证 编译无误下载到开发板后,可以看到D1指示灯不断闪烁,当按下K_UP按键后数据写入到W25Q64芯片内,当按下K_DOWN按键后读取W25Q64芯片的值,同时串口打印出相应信息
2 b+ Q+ N) S5 M1 e
$ w4 a" Y% F' s$ B: _4 X
' l/ m9 r3 J$ @4 ?
转载自: 嵌入式攻城狮% n3 d8 _- F& @% w3 ~
如有侵权请联系删除# h$ {; g! R/ S) B+ w0 R
p$ g: |9 u6 l0 v
% f A/ O* M2 ~) V& A+ I5 T; y8 v# S
|