赞
踩
SPI是一种通讯协议,目的是实现芯片与芯片之间、芯片和传感器之间的数据传输,数据传输可以是8位的也可以是16位的,具体情况具体分析。IIC可以理解成“汉语”,汉语的作用是会汉语的人之间交流的一种语言;SPI可以理解“英语”,英语的作用是会英语的人之间交流的一种语言。
学习SPI时,就是学习SPI的“语法规则”。
SPI通讯是全双工的,主机通过MOSI在发送1位数据时,主机的MISO也会接收1位数据。发送一个字节数据,相应的会接收到一个字节的数据。

根据CPOL(时钟极性)和CPHA(CPHA)的状态的不同,CPOL的值和CPHA的值是通过寄存器来进行设置,每一款带硬件SPI的芯片可以设置CPOL和CPHA,SPI的时序分为四种:
如果 CPOL=0,串行同步时钟的空闲电平状态为低电平;如果CPOL=1,串行同步时钟的空闲电平状态高电平。
如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。


CPOL和CPHA组成成四种时序:

4.1、SPI控制器的框图

数据发送过程
在发送缓冲区中写入字节时,即向SPIx->DR寄存器写入数据时,发送序列开始。在第一个位传输期间,数据字节并行加载到移位寄存器中,然后以串行方式移出到 MOSI 引脚。发送完成,则SPIx->SR寄存器的 TXE置 1,标志在数据从发送缓冲区传输到移位寄存器,标志着传送完成。
数据接收过程
在出现最后一个采样时钟边沿时, RXNE 位置 1, 移位寄存器中接收的数据字节被拷贝到接收缓冲区中。当读取 SPI_DR 寄存器时, SPI 外设将返回此缓冲值。
移位寄存器中的数据将传输到接收缓冲区,并且 RXNE 标志置 1,标志着接收完成。
4.2、SPI器件管理
STM32的SPI通信既可以作为主机,也可以作为从从机,对于主模式和从模式都可通过硬件或软件来进行管理:实现动态切换主/从操作。
硬件管理:通过NSS管脚的电平来决定是主机还是从机
软件管理:通过寄存器的SSI位的值来决定是主机还是从机。
4.3、硬件从器件管理
通过管脚的电平来决定SPI控制器是作为主机还是作为从机。
SSM清0,禁止软件从器件管理,NSS引脚的电平用于决定是主机模式还是 从机模式:NSS接高电平则是主机模式,NSS接低电平则是从机模式。如下图所示

4.4、软件从器件管理
SSM置1,使能软件从器件管理,SSI的值用于决定是主机模式还是从机模式:SSI置1则是主机模式,SSI清零则是从机模式。

本章节,我们是软件管理从器件,并且将SPI控制器配置为主机模式。
5.1、SPI控制器1(SPI_CR1)

位 15 BIDIMODE: 双向通信数据模式使能
0:选择双线单向通信数据模式(全双工)
1:选择单线双向通信数据模式(半双工)
双线:MOSI/MISO
单向:MOSI只能用于数据输出,MISO只能用于数据输入
本章节,主要是学习SPI的读写是选择为“双线单向模式”,所以,该位需要清零。对应的代码:SPI1->CR1&=~(1<<15);
位 14 BIDIOE: 双向通信模式下的输出使能
此位结合 BIDIMODE 位,用于选择双向通信模式下的传输方向
0:禁止MOSI输出
1:使能MOSI输出
注意: 在主模式下,使用 MOSI 引脚
位 11 DFF: 数据帧格式
0:为发送/接收选择 8 位数据帧格式
1:为发送/接收选择 16 位数据帧格式
位 10 RXONLY: 只接收
0:全双工(发送和接收)
1:关闭输出(只接收模式)
需要清零,SPI1->CR1&=~(1<<10);
位 9 SSM: 软件从器件管理
当 SSM 位置 1 时, NSS 引脚输入替换为 SSI 位的值。
0:禁止软件从器件管理,使用硬件从器件管理。
1:使能软件从器件管理
该位需要置1,SPI1->CR1 |= (1<<9);
位 8 SSI: 内部从器件选择
当选择为软件从器件管理时
0:表示SPI控制器为从机
1:表示SPI控制器为主机
该位需要置1,SPI1->CR1 |= (1<<8);
位 7 LSBFIRST: 帧格式
0:先发送数据的最高位
1:先发送数据的最低位
本章节,主要是学习W25Q64的读写,W25Q64要求先传输最高位,该位需要清零SPI1->CR1&=~(1<<7);
位 6 SPE: SPI 使能
0:关闭外设
1:使能外设
该位需要置1
位 5:3 BR[2:0]: 波特率控制
000: fPCLK/2 100: fPCLK/32
001: fPCLK/4 101: fPCLK/64
010: fPCLK/8 110: fPCLK/128
011: fPCLK/16 111: fPCLK/256
fPCLK :是SPI的主时钟,主时钟经过分频后,则是SPI的串行时钟SCK的频率。
SPI1的主时钟是84MHZ
SPI2/SPI3的主时钟是42MHZ
提问:SPI1的SCK时钟配置2.625MHZ,那么这三位需要如何配置?
答:SPI1->CR1 |= (4<<3);
这里的配置需要考虑SPI从设备的波特率。
位 2 MSTR: 主模式选择
0:从配置
1:主配置
本章节,主要是学习W25Q64的从机,STM32作为主机
所以,该位需要置1,SPI1->CR1 |=(1<< 2);
位1 CPOL: 时钟极性
0:空闲状态时, SCK保持低电平
1:空闲状态时, SCK保持高电平
位 0 CPHA: 时钟相位
0:从第一个时钟边沿开始采样数据
1:从第二个时钟边沿开始采样数据
W25Q64支持时钟模式0:CPOL 为0,CPHA为0
W25Q64支持时钟模式3:CPOL 为1,CPHA为1
5.2、SPI状态寄存器
作用记录数据传输过程中的一些状态,我们主要关注的状态:①数据是否发送完成;②数据是否接收完成

位 1 TXE: 发送缓冲区为空 (Transmit buffer empty)
0:发送缓冲区非空
1:发送缓冲区为空,标志着发送已经完成
位 0 RXNE: 接收缓冲区非空 (Receive buffer not empty)
0:接收缓冲区为空
1:接收缓冲区非空,标志着接收到数据。
5.3、SPI数据寄存器

数据寄存器的有效位有16位,给数据寄存器赋值,则数据会通过移位寄存器一位一位发送出去。读取数据寄存器的值,可以接收数据。
5.4、SPI收发数据一体化函数
- //data : 表示即将待发送的数据
- //返回值:表示接收的数据
- u8 SPI1_WriteRead(u8 data)
- {
- //位 1 TXE的值为1,则发送缓冲区为空,标志着发送已经完成
- while(!(SPI1->SR &(1<<1)));
- SPI1->DR = data;
- //位 0 RXNE的值为1,则接收缓冲区非空,标志着接收到数据
- while(!(SPI1->SR &(1<<0)));
- return SPI1->DR;
- }
W25Q64一个存储芯片。通讯接口是SPI接口。
6.1、原理图

6.2、W25Q64的芯片ID
W25Q64的ID号是0XEF16,0xEF表示华邦电子制造,0x16是设备ID。
读取W25Q64芯片ID的时序

对应的代码
- u16 W25Q64_ReadDeviceID(void)
- {
- u16 deviceID;
- FLASH_CS_L;
- SPI1_WriteRead(0x90);
- SPI1_WriteRead(0x00);
- SPI1_WriteRead(0x00);
- SPI1_WriteRead(0x00);
- deviceID = SPI1_WriteRead(0xFF); //读取0xEF
- deviceID = deviceID<<8;
- deviceID |= SPI1_WriteRead(0xFF);//读取0x16,合并成0xEF16
- FLASH_CS_H;
- return deviceID;
- }
6.3、W25Q64的内部存储结构
W25Q64的储存空间是:64Mbit,即8M Byte。
W25Q64的“页”:一页为256 字节。
W25Q64的“扇区”:4K个字节,即4096个字节为一个扇区。共有2048个扇区。
W25Q64的“块”:16个扇区为 1 块,64K个字节为一块,65535个字节,总共有128块。
W25Q64在写入之前必须先查出,使得每个字节空间的数据都为0xFF。
W25Q64每一个字节的空间都有对应的一个地址(编号)。类似于:超市存物柜。


6.4、W25Q64的状态寄存器

BUSY位是个只读位,位于状态寄存器中的S0。在器件中执行“页编程”“扇区擦除”“块区擦除”“芯片擦除”“写状态寄存器”指令时,该位自动置1.这时,除了“读状态寄存器”指令,其他指令都忽略,当编程、擦除和写状态寄存器指令执行完毕之后,该位自动变为0,表示芯片可以接受其它指令
WEL位是只读位,位于状态寄存器中的S1,执行完“写使能”指令后,该位置1,写使能指令将会使状态寄存器WEL位置位,在执行每个页编程,扇区擦除,块区擦除,芯片擦除和写状态寄存器命令之前,都要先置位WEL

- u8 W25Q64_ReadStatusRegister(void)
- {
- u8 status;
- FLASH_CS_L;
- SPI1_WriteRead(0x05);
- status = SPI1_WriteRead(0xFF);
- FLASH_CS_H;
- return status;
- }
6.5、写使能
写使能 指令将会使 状态寄存器 WEL位置位,在执行每个“页编程”“扇区擦除”“块区擦除”“芯片擦除”和“写状态寄存器”命令之前,都要先置位WEL

- void W25Q64_WriteEnable(void)
- {
- FLASH_CS_L;
- SPI1_WriteRead(0x06);
- FLASH_CS_H;
- }
6.6、读数据

当片选CS/拉低之后,紧随其后是一个24位的地址(A23-A0)(24为的地址分3次发送,每次发送1个字节,先发高位)。W25Q64收到地址后,将要读的数据按字节传送给主机,读数据时,W25Q64的地址会自动增加,允许连续的读取多个字节的数据。数据读取完成之后,片选信号/CS拉高。
- 举例:从0x000000地址开始,读取50个字节
- 对应的代码:u8 dataBuf[50] ; //用于保存读取的数据
- W25Q64_ReadDatas(0x000000,dataBuf,50);
- void W25Q64_ReadDatas(u32 addr,u8 *pData,u32 count)
- {
- FLASH_CS_L;
- SPI1_WriteRead(0x03);
- SPI1_WriteRead((addr>>16)&0xFF); //发送地址的高8位
- SPI1_WriteRead((addr>>8)&0xFF);//发送地址的中间8位
- SPI1_WriteRead(addr&0xFF);//发送地址的低8位
- while(count) //循环读取count个字节
- {
- *pData=SPI1_WriteRead(0xFF); //将读取到到一个字节数据保存起来
- pData++; //指针偏移,用于保存下一个字节的数据
- count--;
- }
- FLASH_CS_H;
- }

6.7、扇区擦除
扇区擦除的作用是使得一个扇区的数据全部变为0xFF

扇区擦除指令可以擦除指定一个扇区(4 k字节)内所有数据,使得被擦除的扇区的数据都变为0xFF。写入扇区擦除指令之前必须执行设备写使能(发送设备写使能指令0x06),并判断状态寄存器(状态寄存器位最低位必须等于0才能操作)。发送的扇区擦除指令前,先拉低/CS,接着发送扇区擦除指令码0X20 ,和24位地址(A23-A0),地址发送完毕后,拉高片选线CS/,并判断状态位,等待擦除结束。擦除一个扇区的最少需要150ms时间。
- void W25Q64_EraseSector(u32 addr)
- {
- u32 sectorPosition = addr/4096;
- u32 sectorAddr = sectorPosition*4096;
- FLASH_CS_L;
- W25Q64_WriteEnable(); //写使能
- while(0x01&W25Q64_ReadStatusRegister()); //提取状态寄存器的BUSY位
- SPI1_WriteRead(0x20); //写命令
- SPI1_WriteRead((sectorAddr>>16)&0xFF); //写地址
- SPI1_WriteRead((sectorAddr>>8)&0xFF);
- SPI1_WriteRead(sectorAddr&0xFF);
- FLASH_CS_H;
- while(0x01&W25Q64_ReadStatusRegister()); //提取状态寄存器的BUSY位
- }
6.8、页编程(写数据)

页编程指令允许写多个字节,但不能超过256个字节,页编程之前必须保证内存空间是0XFF,即先擦除再写入。在页编程之前,必须先发送写使能指令。写使能开启后,设备才能接收页编程指令。开启页编程,先拉底/CS,然后发送指令代码“02 h”,接着发送一个24位地址(A23-A0)(发送3次,每次8位)和至少一个字节的数据(数据字节不能超过256字节)。数据字节发送完毕,需要拉高片选线CS/,并判断状态位,等待写入结束。
- 举例:将字符串"Hello world"写入页0
- u8 string[ ] = "Hello world";
- W25Q64_WritePage(0x000000,string,sizeof(string));
- void W25Q64_WritePage(u8 addr,u8 *pData,u8 count)
- {
-
- W25Q64_WriteEnable(); //写使能
- while(0x01&W25Q64_ReadStatus()); //提取状态寄存器的BUSY位
- FLASH_CS_L;
- Spi1_WriteRead(0x02);
- Spi1_WriteRead((addr>>16)&0xFF);
- Spi1_WriteRead((addr>>8)&0xFF);
- Spi1_WriteRead(addr&0xFF);
- while(count)
- {
- Spi1_WriteRead(*pData);
- pData++;
- count --;
- }
- FLASH_CS_H;
- while(0x01&W25Q64_ReadStatus()); //提取状态寄存器的BUSY位
- }

W25Q64一次最多是写一页,需要封装一个函数,实现写n个字节到Flash中(n>256)。
需要判断待写入的数据需要消耗多少“页”,在写的过程,需要动态调节写入的地址,以及写入的数据
- 必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
- 具有自动换页功能
- 在指定地址开始写入指定长度的数据,但是要确保地址不越界!
- ********************************************************************/
- void W25QXX_WriteNoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
- {
- u16 pageremain;
- pageremain=256-WriteAddr%256; //单页剩余的字节数
- if(NumByteToWrite<=pageremain)
- pageremain=NumByteToWrite;//不大于256个字节
- while(1)
- {
- W25q64_WritePage(WriteAddr,pBuffer,pageremain);
- if(NumByteToWrite==pageremain)break;//写入结束了
- else //NumByteToWrite>pageremain
- {
- pBuffer+=pageremain;
- WriteAddr+=pageremain;
-
- NumByteToWrite-=pageremain; //减去已经写入了的字节数
- if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节
- else pageremain=NumByteToWrite; //不够256个字节了
- }
- }
- }

模拟SPI读写W25Q64
原理图

寄存器版
- #include "stm32f4xx.h"
- #include "stdio.h"
- #define FLSH_CS_H (GPIOB->ODR |= 1<<14)
- #define FLSH_CS_L (GPIOB->ODR &=~(1<<14))
- void Delay_us(u16 us)
- {
- SysTick->CTRL &=~(1<<2); //选择时钟源为21MHZ
- SysTick->CTRL &=~(1<<1); //禁止滴答中断
- SysTick->LOAD = 21*us; //设置重装载寄存器的值
- SysTick->CTRL |= (1<<0); //使能滴答定时器
- while(!(SysTick->CTRL&(1<<16)));//阻塞判断定时时间是否到达,判断SysTick->CTRL的位
- }
- /******************** 串口打印函数 ***************************************/
- /* fputc是printf最底层的调用函数 */
- int fputc(int data,FILE *file)
- {
-
- while( !(USART1->SR & (1 << 6)) );//等待发送完成
- USART1->DR = data; //发送数据
- return data;
- }
-
- void usart1_Init()
- {
- u16 integer ;
- u16 fraction;
- float USARTDIV;
- u32 baudRate = 115200;
- RCC->AHB1ENR |= 1<<0;//使能GPIOA的时钟 RCC_AHB1ENR
- GPIOA->MODER &= ~(3<<18);//清零
- GPIOA->MODER |= 2<<18;//设置PA9为复用功能模式MODER
- GPIOA->MODER &= ~(3<<20);//清零
- GPIOA->MODER |= 2<<20;//设置PA10为复用功能模式MODER
- GPIOA->AFR[1]&= ~(0XF<<4);//清零
- GPIOA->AFR[1]|= (7<<4);//设置PA9的复用功能为第7复用功能 RXD, AFRH
- GPIOA->AFR[1]&= ~(0XF<<8);//清零
- GPIOA->AFR[1]|= (7<<8);//设置PA10的复用功能为第7复用功能 TXD, AFRH
-
- RCC->APB2ENR |= 1<<4;//使能串口1模块时钟 RCC_APB2ENR
- USART1->CR1 |= 1<<15;//设置串口OVER8为1
- USART1->CR1 &= ~(1<<12);//设置串口数据位长度 :1 起始位, 8 数据位
- USART1->CR2 &=~(3<<12);//设置串口停止位长度 :1 个停止位
- USART1->CR1 &= ~(1<<10);//无校验
- USART1->CR1 |= 1<<3;//发送使能
- USART1->CR1 |= 1<<2;//接收使能
- USARTDIV = 84000000/8/baudRate;//串口波特率设置:USARTDIV = fCK/(8*(2-OVER8) /波特率
- integer = (u16)USARTDIV;
- fraction = ((u16)(USARTDIV-integer))<<4;
- USART1->BRR |= integer<<4 | fraction;
- USART1->CR1 |= 1<<13;//使能串口
- }
- //FLASH_CS →PB14
- //SPI1_SCK →PB3
- //SPI1_MISO →PB4
- //SPI1_MOSI →PB5
- void SPI1_Init(void)
- {
- RCC->AHB1ENR |= 1<< 1;//使能GPIOB时钟
- GPIOB->MODER &=~(0x3F<<6); //清零
- GPIOB->MODER |= 0x2A<<6; //PB3/4/5选择为复用功能模式
- GPIOB->AFR[0] &=~(0xFFF<<12);//清零
- GPIOB->AFR[0] |= 0x555<<12;//PB3/4/5选择复用功能5→AF5
- //PB14配置为推挽输出
- GPIOB->MODER &=~(3<<28); //清零
- GPIOB->MODER |= 1<<28; //输出模式
- GPIOB->OTYPER &=~(1<<14);//推挽
-
- RCC->APB2ENR |= 1<<12;//使能SPI时钟
- SPI1->CR1 &=~(1<<15);//选择双线单向通信数据模式 :全双工,MOSI发送数据,MISO接收数据
- SPI1->CR1 &=~(1<<11);//配置数据帧格式是8位
- SPI1->CR1 &=~(1<<10);//SPI1控制器全双工通信(发送和接收)
- SPI1->CR1 |= 1<<9;//使能软件从器件管理,SSI的值决定是主机还是从机
- SPI1->CR1 |= 1<<8;//SPI1控制器作为主机
- SPI1->CR1 &=~(1<<7);//设置先送最高位
- SPI1->CR1 |= (4<<3);//配置波特率2.625MHZ
- SPI1->CR1 |= (1<<2);//主配置 : 配置为主机
- //选择时钟模式0,CPOL和CPHA都要清零
- SPI1->CR1 &=~(1<<1);
- SPI1->CR1 &=~(1<<0);
- SPI1->CR1 |= 1<<6;//使能SPI1
- FLSH_CS_H; //片选拉高,表示“取消选择”
- }
- //data:待发送的数据
- //返回值:是接收到的数据
- u8 SPI1_SendReveiveData(u8 data)
- {
- u8 recvData = 0;
- while(!(SPI1->SR&(1<<1))); //发送完成,跳出该循环
- SPI1->DR = data; //SPI1发送一个字节数据,SPI1_DR寄存器执行写操作将 TXE 位清零。
- while(!(SPI1->SR&(1<<0))); //接收完成,跳出该循环
- recvData =SPI1->DR ; //读取接收到的一个字节数据,通过读取 SPI1_DR 寄存器将 RXNE 位清零。
- return recvData;
- }
- //返回值:厂商号+ID号 0xEF16
- u16 W25Q64_ReadID(void)
- {
- u8 mID = 0; //厂商号
- u8 dID = 0; //设备号
- FLSH_CS_L;//拉低片选:表示选择
- SPI1_SendReveiveData(0x90);//发送指令代码“0x90”
- SPI1_SendReveiveData(0x00);//发送地址“0x00” ,发送最高的字节
- SPI1_SendReveiveData(0x00);//发送地址“0x00” ,发送中间的字节
- SPI1_SendReveiveData(0x00);//发送地址“0x00” ,发送最低的字节
- mID = SPI1_SendReveiveData(0xFF);//读厂商号,0xEF
- dID = SPI1_SendReveiveData(0xFF);//读取ID号,0x16
- FLSH_CS_H;//拉高片选:表示取消选择
- return mID<<8|dID;//返回厂商号+ID号 0xEF16
- }
-
- u8 W25Q64_ReadStatus(void)
- {
- u8 status = 0;
- FLSH_CS_L;//片选拉低
- SPI1_SendReveiveData(0x05);//STM32发送“读状态寄存器指令”→0x05给W25Q64
- status = SPI1_SendReveiveData(0xFF);//STM32读取状态寄存器
- FLSH_CS_H;//片选管脚拉高
- return status;
- }
-
- void W25Q64_WriteEnable(void)
- {
- FLSH_CS_L;//片选拉低
- SPI1_SendReveiveData(0x06);//STM32发送“写使能指令”→0x06给W25Q64
- FLSH_CS_H;//片选管脚拉高
- }
- //addr:地址
- //pBuf:读数据的存储区
- //count :要读多少给字节
- void W25Q64_ReadData(u32 addr,u8*pBuf,u32 count)
- {
- FLSH_CS_L;//拉低片选
- SPI1_SendReveiveData(0x03);//发送“读数据指令”→0x03
- //MOSI发送要读取的首地址,注意:高位先发送。
- SPI1_SendReveiveData((addr>>16)&0xFF);
- SPI1_SendReveiveData((addr>>8)&0xFF);
- SPI1_SendReveiveData(addr&0xFF);
- //MISO可以读取到数据,可以连续读,W25Q64内部地址会自动递增。
- while(count--)
- {
- *pBuf = SPI1_SendReveiveData(0xFF);
- pBuf++;
- }
- FLSH_CS_H;//拉高片选
- }
-
- void W25Q64_EraseSector(u32 addr)
- {
- W25Q64_WriteEnable();//执行写使能指令,
- while(!((1<<1)&W25Q64_ReadStatus()));//读取状态寄存器,判断WEL位,判断是否已经进入“写使能状态”
- FLSH_CS_L;//拉低片选
- SPI1_SendReveiveData(0x20);//发送“扇区擦除指令”→0x20
- //MOSI发送要扇区的首地址,注意:高位先发送。
- SPI1_SendReveiveData((addr>>16)&0xFF);
- SPI1_SendReveiveData((addr>>8)&0xFF);
- SPI1_SendReveiveData(addr&0xFF);
- FLSH_CS_H;//拉高片选
- //读取状态寄存器,判断“擦除动作”是否忙了。只有忙完“擦除”之后,才可以去执行其他操作。
- while(((1<<0)&W25Q64_ReadStatus()));
- }
-
- void W25Q64_EraseChip(void)
- {
- W25Q64_WriteEnable();//执行写使能指令,
- while(!((1<<1)&W25Q64_ReadStatus()));//读取状态寄存器,判断WEL位,判断是否已经进入“写使能状态”
- FLSH_CS_L;//拉低片选
- SPI1_SendReveiveData(0xC7);//3)发送“芯片擦除指令”→0xC7
- FLSH_CS_H;//拉高片选
- //读取状态寄存器,判断“擦除动作”是否忙了。只有忙完“擦除”之后,才可以去执行其他操作。
- while(((1<<0)&W25Q64_ReadStatus()));
- }
-
-
- u8 W25Q64_WritePage(u32 addr,u8 *pData,u16 count)
- {
- if(count>256)
- {
- return 1;
- }
- W25Q64_WriteEnable();//执行写使能指令,
- while(!((1<<1)&W25Q64_ReadStatus()));//读取状态寄存器,判断WEL位,判断是否已经进入“写使能状态”
- FLSH_CS_L;//拉低片选
- SPI1_SendReveiveData(0x02);//发送“页编程指令”→0x02
- //MOSI发送要首地址,注意:高位先发送。
- SPI1_SendReveiveData((addr>>16)&0xFF);
- SPI1_SendReveiveData((addr>>8)&0xFF);
- SPI1_SendReveiveData(addr&0xFF);
- //MOSI发送数据。W25Q64内部地址自动递增。发送多个字节的数据需要通过循环来完成。
- while(count--)
- {
- SPI1_SendReveiveData(*pData);
- pData++;
- }
- FLSH_CS_H;//拉高片选
- //读取状态寄存器,判断“页编程动作”是否忙了。只有忙完“页编程”之后,才可以去执行其他操作。
- while(((1<<0)&W25Q64_ReadStatus()));
- return 0;
- }
-
- int main(void)
- {u8 writeBuf[ ] = {"欢度中秋"};
- u8 readBuf[50] ={0};
-
- u16 ID = 0x0000;
- SPI1_Init();
- usart1_Init();//初始化串口
- ID = W25Q64_ReadID();
- printf("W25Q64的ID号是0x%x\r\n",ID);
- W25Q64_EraseSector(0x000000);
- W25Q64_WritePage(0x000000,writeBuf,sizeof(writeBuf));
- printf("写入到W25Q64的数据是 →%s\r\n",writeBuf);
- W25Q64_ReadData(0x000000,readBuf,sizeof(writeBuf)) ;
- printf("从W25Q64的读取到数据是 →%s\r\n",readBuf);
- while(1)
- {
-
- }
- }

库函数版
- #include "stm32f4xx.h"
- #include "stdio.h"
- #define FLSH_CS_H (GPIOB->ODR |= 1<<14)
- #define FLSH_CS_L (GPIOB->ODR &=~(1<<14))
- void Delay_us(u16 us)
- {
- SysTick->CTRL &=~(1<<2); //选择时钟源为21MHZ
- SysTick->CTRL &=~(1<<1); //禁止滴答中断
- SysTick->LOAD = 21*us; //设置重装载寄存器的值
- SysTick->CTRL |= (1<<0); //使能滴答定时器
- while(!(SysTick->CTRL&(1<<16)));//阻塞判断定时时间是否到达,判断SysTick->CTRL的位
- }
- /******************** 串口打印函数 ***************************************/
- /* fputc是printf最底层的调用函数 */
- int fputc(int data,FILE *file)
- {
-
- while( !(USART1->SR & (1 << 6)) );//等待发送完成
- USART1->DR = data; //发送数据
- return data;
- }
-
- void usart1_Init()
- {
- u32 baudRate = 115200;
- GPIO_InitTypeDef GPIO_InitStructe;
- USART_InitTypeDef USART_InitStructe;
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能 A 端口
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //使能串口端口
- GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIO 端口映射到 USART
- GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIO 端口映射到 USART
- // USART_DeInit(USART1); //复位
- GPIO_InitStructe.GPIO_Mode =GPIO_Mode_AF; //配置为输出模式
- GPIO_InitStructe.GPIO_Pin =GPIO_Pin_9|GPIO_Pin_10; //初始化 GPIOA9/10;
- GPIO_Init(GPIOA,&GPIO_InitStructe);
- USART_InitStructe.USART_BaudRate =baudRate; //波特率
- USART_InitStructe.USART_HardwareFlowControl =USART_HardwareFlowControl_None;//无硬件流控制
- USART_InitStructe.USART_Mode =USART_Mode_Tx|USART_Mode_Rx; //接收模式,发送模式使能
- USART_InitStructe.USART_Parity =USART_Parity_No; //无奇偶校验
- USART_InitStructe.USART_StopBits =USART_StopBits_1; //一位停止位
- USART_InitStructe.USART_WordLength =USART_WordLength_8b;//8 位数据位
- USART_Init(USART1,&USART_InitStructe);
- USART_Cmd(USART1,ENABLE); //使能串口
- }
- //FLASH_CS →PB14
- //SPI1_SCK →PB3
- //SPI1_MISO →PB4
- //SPI1_MOSI →PB5
- void SPI1_Init(void)
- {
- GPIO_InitTypeDef GPIO_InitStructure;
- SPI_InitTypeDef SPI_InitStructure;
- GPIO_InitTypeDef SPI_GPIO_InitStruct;
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能 GPIOB 时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//使能 SPI1 时钟
- //GPIOFB3,4,5 初始化设置
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//PB3~5 复用功能输出
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
- GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
- GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3 复用为 SPI1
- GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB4 复用为 SPI1
- GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB5 复用为 SPI1
- SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置 SPI 单向或者双向的数据模式:SPI 设置为双线双向全双工
- SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置 SPI 工作模式:设置为主 SPI
- SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置 SPI 的数据大小:SPI 发送接收8 位帧结构
- SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
- SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
- SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS 信号由硬件(NSS 管脚)还是软件(使用SSI 位)管理:内部 NSS 信号有 SSI 位控制
- SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; //定义波特率预分频的值:波特率预分频值为 32
- SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从 MSB 位还是 LSB 位开始:数据传输从 MSB 位开始
- SPI_Init(SPI1, &SPI_InitStructure); //根据 SPI_InitStruct 中指定的参数初始化外设 SPIx 寄存器
- SPI_Cmd(SPI1, ENABLE); //使能 SPI 外设
-
- SPI_GPIO_InitStruct.GPIO_Mode=GPIO_Mode_OUT;
- SPI_GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
- SPI_GPIO_InitStruct.GPIO_Pin=GPIO_Pin_14;
- SPI_GPIO_InitStruct.GPIO_Speed=GPIO_Fast_Speed;
- GPIO_Init(GPIOB,&SPI_GPIO_InitStruct);
- GPIO_SetBits(GPIOB,GPIO_Pin_14);
- }
- //data:待发送的数据
- //返回值:是接收到的数据
- u8 SPI1_SendReveiveData(u8 data)
- {
- while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
- SPI_I2S_SendData(SPI1, data);
- while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
- return SPI_I2S_ReceiveData(SPI1);
- }
- //返回值:厂商号+ID号 0xEF16
- u16 W25Q64_ReadID(void)
- {
- u8 mID = 0; //厂商号
- u8 dID = 0; //设备号
- FLSH_CS_L;//拉低片选:表示选择
- SPI1_SendReveiveData(0x90);//发送指令代码“0x90”
- SPI1_SendReveiveData(0x00);//发送地址“0x00” ,发送最高的字节
- SPI1_SendReveiveData(0x00);//发送地址“0x00” ,发送中间的字节
- SPI1_SendReveiveData(0x00);//发送地址“0x00” ,发送最低的字节
- mID = SPI1_SendReveiveData(0xFF);//读厂商号,0xEF
- dID = SPI1_SendReveiveData(0xFF);//读取ID号,0x16
- FLSH_CS_H;//拉高片选:表示取消选择
- return mID<<8|dID;//返回厂商号+ID号 0xEF16
- }
-
- u8 W25Q64_ReadStatus(void)
- {
- u8 status = 0;
- FLSH_CS_L;//片选拉低
- SPI1_SendReveiveData(0x05);//STM32发送“读状态寄存器指令”→0x05给W25Q64
- status = SPI1_SendReveiveData(0xFF);//STM32读取状态寄存器
- FLSH_CS_H;//片选管脚拉高
- return status;
- }
-
- void W25Q64_WriteEnable(void)
- {
- FLSH_CS_L;//片选拉低
- SPI1_SendReveiveData(0x06);//STM32发送“写使能指令”→0x06给W25Q64
- FLSH_CS_H;//片选管脚拉高
- }
- //addr:地址
- //pBuf:读数据的存储区
- //count :要读多少给字节
- void W25Q64_ReadData(u32 addr,u8*pBuf,u32 count)
- {
- FLSH_CS_L;//拉低片选
- SPI1_SendReveiveData(0x03);//发送“读数据指令”→0x03
- //MOSI发送要读取的首地址,注意:高位先发送。
- SPI1_SendReveiveData((addr>>16)&0xFF);
- SPI1_SendReveiveData((addr>>8)&0xFF);
- SPI1_SendReveiveData(addr&0xFF);
- //MISO可以读取到数据,可以连续读,W25Q64内部地址会自动递增。
- while(count--)
- {
- *pBuf = SPI1_SendReveiveData(0xFF);
- pBuf++;
- }
- FLSH_CS_H;//拉高片选
- }
-
- void W25Q64_EraseSector(u32 addr)
- {
- W25Q64_WriteEnable();//执行写使能指令,
- while(!((1<<1)&W25Q64_ReadStatus()));//读取状态寄存器,判断WEL位,判断是否已经进入“写使能状态”
- FLSH_CS_L;//拉低片选
- SPI1_SendReveiveData(0x20);//发送“扇区擦除指令”→0x20
- //MOSI发送要扇区的首地址,注意:高位先发送。
- SPI1_SendReveiveData((addr>>16)&0xFF);
- SPI1_SendReveiveData((addr>>8)&0xFF);
- SPI1_SendReveiveData(addr&0xFF);
- FLSH_CS_H;//拉高片选
- //读取状态寄存器,判断“擦除动作”是否忙了。只有忙完“擦除”之后,才可以去执行其他操作。
- while(((1<<0)&W25Q64_ReadStatus()));
- }
-
- void W25Q64_EraseChip(void)
- {
- W25Q64_WriteEnable();//执行写使能指令,
- while(!((1<<1)&W25Q64_ReadStatus()));//读取状态寄存器,判断WEL位,判断是否已经进入“写使能状态”
- FLSH_CS_L;//拉低片选
- SPI1_SendReveiveData(0xC7);//3)发送“芯片擦除指令”→0xC7
- FLSH_CS_H;//拉高片选
- //读取状态寄存器,判断“擦除动作”是否忙了。只有忙完“擦除”之后,才可以去执行其他操作。
- while(((1<<0)&W25Q64_ReadStatus()));
- }
-
-
- u8 W25Q64_WritePage(u32 addr,u8 *pData,u16 count)
- {
- if(count>256)
- {
- return 1;
- }
- W25Q64_WriteEnable();//执行写使能指令,
- while(!((1<<1)&W25Q64_ReadStatus()));//读取状态寄存器,判断WEL位,判断是否已经进入“写使能状态”
- FLSH_CS_L;//拉低片选
- SPI1_SendReveiveData(0x02);//发送“页编程指令”→0x02
- //MOSI发送要首地址,注意:高位先发送。
- SPI1_SendReveiveData((addr>>16)&0xFF);
- SPI1_SendReveiveData((addr>>8)&0xFF);
- SPI1_SendReveiveData(addr&0xFF);
- //MOSI发送数据。W25Q64内部地址自动递增。发送多个字节的数据需要通过循环来完成。
- while(count--)
- {
- SPI1_SendReveiveData(*pData);
- pData++;
- }
- FLSH_CS_H;//拉高片选
- //读取状态寄存器,判断“页编程动作”是否忙了。只有忙完“页编程”之后,才可以去执行其他操作。
- while(((1<<0)&W25Q64_ReadStatus()));
- return 0;
- }
-
- int main(void)
- {u8 writeBuf[ ] = {"欢度中秋"};
- u8 readBuf[50] ={0};
-
- u16 ID = 0x0000;
- SPI1_Init();
- usart1_Init();//初始化串口
- ID = W25Q64_ReadID();
- printf("W25Q64的ID号是0x%x\r\n",ID);
- W25Q64_EraseSector(0x000000);
- W25Q64_WritePage(0x000000,writeBuf,sizeof(writeBuf));
- printf("写入到W25Q64的数据是 →%s\r\n",writeBuf);
- W25Q64_ReadData(0x000000,readBuf,sizeof(writeBuf)) ;
- printf("从W25Q64的读取到数据是 →%s\r\n",readBuf);
- while(1)
- {
-
- }
- }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。