当前位置:   article > 正文

stm32(十五)SPI_stm32 spi

stm32 spi

1、SPI介绍

SPI是一种通讯协议,目的是实现芯片与芯片之间、芯片和传感器之间的数据传输,数据传输可以是8位的也可以是16位的,具体情况具体分析。IIC可以理解成“汉语”,汉语的作用是会汉语的人之间交流的一种语言;SPI可以理解“英语”,英语的作用是会英语的人之间交流的一种语言。

学习SPI时,就是学习SPI的“语法规则”。

SPI通讯是全双工的,主机通过MOSI在发送1位数据时,主机的MISO也会接收1位数据。发送一个字节数据,相应的会接收到一个字节的数据。

2、SPI的总线接线图

  • SPI通讯需要四个管脚:SCK(serial clock串行时钟,由主控产生脉冲方波时钟);MOSI(master output salve input,主机将数据一位一位的传输给从机的数据通道)/MISO(master input salve output,从机将数据一位一位的传输给主机的数据通道)/CS(chip select,低电平表示选择,高电平表示释放)。
  • SPI总线可以挂载多个设备,如何区分多个设备呢?就是通过CS管脚来进行区分。每一个SPI从设备都对应有一个CS片选脚。主控和SPI从设备进行通信时,首先会将相应的从设备的片选管脚拉低。

3、SPI的时序

根据CPOL(时钟极性)和CPHA(CPHA)的状态的不同,CPOL的值和CPHA的值是通过寄存器来进行设置,每一款带硬件SPI的芯片可以设置CPOL和CPHA,SPI的时序分为四种:

如果 CPOL=0,串行同步时钟的空闲电平状态为低电平;如果CPOL=1,串行同步时钟的空闲电平状态高电平。

如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。

CPOL和CPHA组成成四种时序:

4、STM32F407ZGT6内部的硬件SPI控制器

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、相关寄存器

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收发数据一体化函数

  1. //data : 表示即将待发送的数据
  2. //返回值:表示接收的数据
  3. u8 SPI1_WriteRead(u8 data)
  4. {
  5. //1 TXE的值为1,则发送缓冲区为空,标志着发送已经完成
  6. while(!(SPI1->SR &(1<<1)));
  7. SPI1->DR = data;
  8. //0 RXNE的值为1,则接收缓冲区非空,标志着接收到数据
  9. while(!(SPI1->SR &(1<<0)));
  10. return SPI1->DR;
  11. }

6、W25Q64

W25Q64一个存储芯片。通讯接口是SPI接口。

6.1、原理图

6.2、W25Q64的芯片ID

W25Q64的ID号是0XEF16,0xEF表示华邦电子制造,0x16是设备ID。

读取W25Q64芯片ID的时序

对应的代码

  1. u16 W25Q64_ReadDeviceID(void)
  2. {
  3. u16 deviceID;
  4. FLASH_CS_L;
  5. SPI1_WriteRead(0x90);
  6. SPI1_WriteRead(0x00);
  7. SPI1_WriteRead(0x00);
  8. SPI1_WriteRead(0x00);
  9. deviceID = SPI1_WriteRead(0xFF); //读取0xEF
  10. deviceID = deviceID<<8;
  11. deviceID |= SPI1_WriteRead(0xFF);//读取0x16,合并成0xEF16
  12. FLASH_CS_H;
  13. return deviceID;
  14. }

6.3、W25Q64的内部存储结构

W25Q64的储存空间是:64Mbit,即8M Byte。

W25Q64的“页”:一页为256 字节。

W25Q64的“扇区”:4K个字节,即4096个字节为一个扇区。共有2048个扇区。

W25Q64的“块”:16个扇区为 1 块,64K个字节为一块,65535个字节,总共有128块。

W25Q64在写入之前必须先查出,使得每个字节空间的数据都为0xFF。

  • W25Q64寻址

W25Q64每一个字节的空间都有对应的一个地址(编号)。类似于:超市存物柜。

  • 块&扇区&页的结构图

  • 整个W25Q64的存储结构图

6.4、W25Q64的状态寄存器

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

WEL位是只读位,位于状态寄存器中的S1,执行完“写使能”指令后,该位置1,写使能指令将会使状态寄存器WEL位置位,在执行每个页编程,扇区擦除,块区擦除,芯片擦除和写状态寄存器命令之前,都要先置位WEL

  • 读取状态寄存器的时序

  • 对应代码
  1. u8 W25Q64_ReadStatusRegister(void)
  2. {
  3. u8 status;
  4. FLASH_CS_L;
  5. SPI1_WriteRead(0x05);
  6. status = SPI1_WriteRead(0xFF);
  7. FLASH_CS_H;
  8. return status;
  9. }

6.5、写使能

写使能 指令将会使 状态寄存器 WEL位置位,在执行每个“页编程”“扇区擦除”“块区擦除”“芯片擦除”和“写状态寄存器”命令之前,都要先置位WEL

  • 写使能时序

  • 对应代码
  1. void W25Q64_WriteEnable(void)
  2. {
  3. FLASH_CS_L;
  4. SPI1_WriteRead(0x06);
  5. FLASH_CS_H;
  6. }

6.6、读数据

  • 读数据时序

当片选CS/拉低之后,紧随其后是一个24位的地址(A23-A0)(24为的地址分3次发送,每次发送1个字节,先发高位)。W25Q64收到地址后,将要读的数据按字节传送给主机,读数据时,W25Q64的地址会自动增加,允许连续的读取多个字节的数据。数据读取完成之后,片选信号/CS拉高。

  • 对应的代码
  1. 举例:从0x000000地址开始,读取50个字节
  2. 对应的代码:u8 dataBuf[50] ; //用于保存读取的数据
  3. W25Q64_ReadDatas(0x000000,dataBuf,50);
  4. void W25Q64_ReadDatas(u32 addr,u8 *pData,u32 count)
  5. {
  6. FLASH_CS_L;
  7. SPI1_WriteRead(0x03);
  8. SPI1_WriteRead((addr>>16)&0xFF); //发送地址的高8位
  9. SPI1_WriteRead((addr>>8)&0xFF);//发送地址的中间8位
  10. SPI1_WriteRead(addr&0xFF);//发送地址的低8
  11. while(count) //循环读取count个字节
  12. {
  13. *pData=SPI1_WriteRead(0xFF); //将读取到到一个字节数据保存起来
  14. pData++; //指针偏移,用于保存下一个字节的数据
  15. count--;
  16. }
  17. FLASH_CS_H;
  18. }

6.7、扇区擦除

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

  • 对应时序

扇区擦除指令可以擦除指定一个扇区(4 k字节)内所有数据,使得被擦除的扇区的数据都变为0xFF。写入扇区擦除指令之前必须执行设备写使能(发送设备写使能指令0x06),并判断状态寄存器(状态寄存器位最低位必须等于0才能操作)。发送的扇区擦除指令前,先拉低/CS,接着发送扇区擦除指令码0X20 ,和24位地址(A23-A0),地址发送完毕后,拉高片选线CS/,并判断状态位,等待擦除结束。擦除一个扇区的最少需要150ms时间。

  • 对应代码
  1. void W25Q64_EraseSector(u32 addr)
  2. {
  3. u32 sectorPosition = addr/4096;
  4. u32 sectorAddr = sectorPosition*4096;
  5. FLASH_CS_L;
  6. W25Q64_WriteEnable(); //写使能
  7. while(0x01&W25Q64_ReadStatusRegister()); //提取状态寄存器的BUSY位
  8. SPI1_WriteRead(0x20); //写命令
  9. SPI1_WriteRead((sectorAddr>>16)&0xFF); //写地址
  10. SPI1_WriteRead((sectorAddr>>8)&0xFF);
  11. SPI1_WriteRead(sectorAddr&0xFF);
  12. FLASH_CS_H;
  13. while(0x01&W25Q64_ReadStatusRegister()); //提取状态寄存器的BUSY位
  14. }

6.8、页编程(写数据)

  • 时序

页编程指令允许写多个字节,但不能超过256个字节,页编程之前必须保证内存空间是0XFF,即先擦除再写入。在页编程之前,必须先发送写使能指令。写使能开启后,设备才能接收页编程指令。开启页编程,先拉底/CS,然后发送指令代码“02 h”,接着发送一个24位地址(A23-A0)(发送3次,每次8位)和至少一个字节的数据(数据字节不能超过256字节)。数据字节发送完毕,需要拉高片选线CS/,并判断状态位,等待写入结束。

  • 对应代码
  1. 举例:将字符串"Hello world"写入页0
  2. u8 string[ ] = "Hello world";
  3. W25Q64_WritePage(0x000000,string,sizeof(string));
  4. void W25Q64_WritePage(u8 addr,u8 *pData,u8 count)
  5. {
  6. W25Q64_WriteEnable(); //写使能
  7. while(0x01&W25Q64_ReadStatus()); //提取状态寄存器的BUSY位
  8. FLASH_CS_L;
  9. Spi1_WriteRead(0x02);
  10. Spi1_WriteRead((addr>>16)&0xFF);
  11. Spi1_WriteRead((addr>>8)&0xFF);
  12. Spi1_WriteRead(addr&0xFF);
  13. while(count)
  14. {
  15. Spi1_WriteRead(*pData);
  16. pData++;
  17. count --;
  18. }
  19. FLASH_CS_H;
  20. while(0x01&W25Q64_ReadStatus()); //提取状态寄存器的BUSY位
  21. }

7、写FLASH函数封装

W25Q64一次最多是写一页,需要封装一个函数,实现写n个字节到Flash中(n>256)。

需要判断待写入的数据需要消耗多少“页”,在写的过程,需要动态调节写入的地址,以及写入的数据

  1. 必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
  2. 具有自动换页功能
  3. 在指定地址开始写入指定长度的数据,但是要确保地址不越界!
  4. ********************************************************************/
  5. void W25QXX_WriteNoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
  6. {
  7. u16 pageremain;
  8. pageremain=256-WriteAddr%256; //单页剩余的字节数
  9. if(NumByteToWrite<=pageremain)
  10. pageremain=NumByteToWrite;//不大于256个字节
  11. while(1)
  12. {
  13. W25q64_WritePage(WriteAddr,pBuffer,pageremain);
  14. if(NumByteToWrite==pageremain)break;//写入结束了
  15. else //NumByteToWrite>pageremain
  16. {
  17. pBuffer+=pageremain;
  18. WriteAddr+=pageremain;
  19. NumByteToWrite-=pageremain; //减去已经写入了的字节数
  20. if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节
  21. else pageremain=NumByteToWrite; //不够256个字节了
  22. }
  23. }
  24. }

8、软件设计

模拟SPI读写W25Q64

原理图

寄存器版

  1. #include "stm32f4xx.h"
  2. #include "stdio.h"
  3. #define FLSH_CS_H (GPIOB->ODR |= 1<<14)
  4. #define FLSH_CS_L (GPIOB->ODR &=~(1<<14))
  5. void Delay_us(u16 us)
  6. {
  7. SysTick->CTRL &=~(1<<2); //选择时钟源为21MHZ
  8. SysTick->CTRL &=~(1<<1); //禁止滴答中断
  9. SysTick->LOAD = 21*us; //设置重装载寄存器的值
  10. SysTick->CTRL |= (1<<0); //使能滴答定时器
  11. while(!(SysTick->CTRL&(1<<16)));//阻塞判断定时时间是否到达,判断SysTick->CTRL的位
  12. }
  13. /******************** 串口打印函数 ***************************************/
  14. /* fputc是printf最底层的调用函数 */
  15. int fputc(int data,FILE *file)
  16. {
  17. while( !(USART1->SR & (1 << 6)) );//等待发送完成
  18. USART1->DR = data; //发送数据
  19. return data;
  20. }
  21. void usart1_Init()
  22. {
  23. u16 integer ;
  24. u16 fraction;
  25. float USARTDIV;
  26. u32 baudRate = 115200;
  27. RCC->AHB1ENR |= 1<<0;//使能GPIOA的时钟 RCC_AHB1ENR
  28. GPIOA->MODER &= ~(3<<18);//清零
  29. GPIOA->MODER |= 2<<18;//设置PA9为复用功能模式MODER
  30. GPIOA->MODER &= ~(3<<20);//清零
  31. GPIOA->MODER |= 2<<20;//设置PA10为复用功能模式MODER
  32. GPIOA->AFR[1]&= ~(0XF<<4);//清零
  33. GPIOA->AFR[1]|= (7<<4);//设置PA9的复用功能为第7复用功能 RXD, AFRH
  34. GPIOA->AFR[1]&= ~(0XF<<8);//清零
  35. GPIOA->AFR[1]|= (7<<8);//设置PA10的复用功能为第7复用功能 TXD, AFRH
  36. RCC->APB2ENR |= 1<<4;//使能串口1模块时钟 RCC_APB2ENR
  37. USART1->CR1 |= 1<<15;//设置串口OVER81
  38. USART1->CR1 &= ~(1<<12);//设置串口数据位长度 :1 起始位, 8 数据位
  39. USART1->CR2 &=~(3<<12);//设置串口停止位长度 :1 个停止位
  40. USART1->CR1 &= ~(1<<10);//无校验
  41. USART1->CR1 |= 1<<3;//发送使能
  42. USART1->CR1 |= 1<<2;//接收使能
  43. USARTDIV = 84000000/8/baudRate;//串口波特率设置:USARTDIV = fCK/(8*(2-OVER8) /波特率
  44. integer = (u16)USARTDIV;
  45. fraction = ((u16)(USARTDIV-integer))<<4;
  46. USART1->BRR |= integer<<4 | fraction;
  47. USART1->CR1 |= 1<<13;//使能串口
  48. }
  49. //FLASH_CS →PB14
  50. //SPI1_SCK →PB3
  51. //SPI1_MISO →PB4
  52. //SPI1_MOSI →PB5
  53. void SPI1_Init(void)
  54. {
  55. RCC->AHB1ENR |= 1<< 1;//使能GPIOB时钟
  56. GPIOB->MODER &=~(0x3F<<6); //清零
  57. GPIOB->MODER |= 0x2A<<6; //PB3/4/5选择为复用功能模式
  58. GPIOB->AFR[0] &=~(0xFFF<<12);//清零
  59. GPIOB->AFR[0] |= 0x555<<12;//PB3/4/5选择复用功能5→AF5
  60. //PB14配置为推挽输出
  61. GPIOB->MODER &=~(3<<28); //清零
  62. GPIOB->MODER |= 1<<28; //输出模式
  63. GPIOB->OTYPER &=~(1<<14);//推挽
  64. RCC->APB2ENR |= 1<<12;//使能SPI时钟
  65. SPI1->CR1 &=~(1<<15);//选择双线单向通信数据模式 :全双工,MOSI发送数据,MISO接收数据
  66. SPI1->CR1 &=~(1<<11);//配置数据帧格式是8
  67. SPI1->CR1 &=~(1<<10);//SPI1控制器全双工通信(发送和接收)
  68. SPI1->CR1 |= 1<<9;//使能软件从器件管理,SSI的值决定是主机还是从机
  69. SPI1->CR1 |= 1<<8;//SPI1控制器作为主机
  70. SPI1->CR1 &=~(1<<7);//设置先送最高位
  71. SPI1->CR1 |= (4<<3);//配置波特率2.625MHZ
  72. SPI1->CR1 |= (1<<2);//主配置 : 配置为主机
  73. //选择时钟模式0,CPOL和CPHA都要清零
  74. SPI1->CR1 &=~(1<<1);
  75. SPI1->CR1 &=~(1<<0);
  76. SPI1->CR1 |= 1<<6;//使能SPI1
  77. FLSH_CS_H; //片选拉高,表示“取消选择”
  78. }
  79. //data:待发送的数据
  80. //返回值:是接收到的数据
  81. u8 SPI1_SendReveiveData(u8 data)
  82. {
  83. u8 recvData = 0;
  84. while(!(SPI1->SR&(1<<1))); //发送完成,跳出该循环
  85. SPI1->DR = data; //SPI1发送一个字节数据,SPI1_DR寄存器执行写操作将 TXE 位清零。
  86. while(!(SPI1->SR&(1<<0))); //接收完成,跳出该循环
  87. recvData =SPI1->DR ; //读取接收到的一个字节数据,通过读取 SPI1_DR 寄存器将 RXNE 位清零。
  88. return recvData;
  89. }
  90. //返回值:厂商号+ID号 0xEF16
  91. u16 W25Q64_ReadID(void)
  92. {
  93. u8 mID = 0; //厂商号
  94. u8 dID = 0; //设备号
  95. FLSH_CS_L;//拉低片选:表示选择
  96. SPI1_SendReveiveData(0x90);//发送指令代码“0x90
  97. SPI1_SendReveiveData(0x00);//发送地址“0x00” ,发送最高的字节
  98. SPI1_SendReveiveData(0x00);//发送地址“0x00” ,发送中间的字节
  99. SPI1_SendReveiveData(0x00);//发送地址“0x00” ,发送最低的字节
  100. mID = SPI1_SendReveiveData(0xFF);//读厂商号,0xEF
  101. dID = SPI1_SendReveiveData(0xFF);//读取ID号,0x16
  102. FLSH_CS_H;//拉高片选:表示取消选择
  103. return mID<<8|dID;//返回厂商号+ID号 0xEF16
  104. }
  105. u8 W25Q64_ReadStatus(void)
  106. {
  107. u8 status = 0;
  108. FLSH_CS_L;//片选拉低
  109. SPI1_SendReveiveData(0x05);//STM32发送“读状态寄存器指令”→0x05给W25Q64
  110. status = SPI1_SendReveiveData(0xFF);//STM32读取状态寄存器
  111. FLSH_CS_H;//片选管脚拉高
  112. return status;
  113. }
  114. void W25Q64_WriteEnable(void)
  115. {
  116. FLSH_CS_L;//片选拉低
  117. SPI1_SendReveiveData(0x06);//STM32发送“写使能指令”→0x06给W25Q64
  118. FLSH_CS_H;//片选管脚拉高
  119. }
  120. //addr:地址
  121. //pBuf:读数据的存储区
  122. //count :要读多少给字节
  123. void W25Q64_ReadData(u32 addr,u8*pBuf,u32 count)
  124. {
  125. FLSH_CS_L;//拉低片选
  126. SPI1_SendReveiveData(0x03);//发送“读数据指令”→0x03
  127. //MOSI发送要读取的首地址,注意:高位先发送。
  128. SPI1_SendReveiveData((addr>>16)&0xFF);
  129. SPI1_SendReveiveData((addr>>8)&0xFF);
  130. SPI1_SendReveiveData(addr&0xFF);
  131. //MISO可以读取到数据,可以连续读,W25Q64内部地址会自动递增。
  132. while(count--)
  133. {
  134. *pBuf = SPI1_SendReveiveData(0xFF);
  135. pBuf++;
  136. }
  137. FLSH_CS_H;//拉高片选
  138. }
  139. void W25Q64_EraseSector(u32 addr)
  140. {
  141. W25Q64_WriteEnable();//执行写使能指令,
  142. while(!((1<<1)&W25Q64_ReadStatus()));//读取状态寄存器,判断WEL位,判断是否已经进入“写使能状态”
  143. FLSH_CS_L;//拉低片选
  144. SPI1_SendReveiveData(0x20);//发送“扇区擦除指令”→0x20
  145. //MOSI发送要扇区的首地址,注意:高位先发送。
  146. SPI1_SendReveiveData((addr>>16)&0xFF);
  147. SPI1_SendReveiveData((addr>>8)&0xFF);
  148. SPI1_SendReveiveData(addr&0xFF);
  149. FLSH_CS_H;//拉高片选
  150. //读取状态寄存器,判断“擦除动作”是否忙了。只有忙完“擦除”之后,才可以去执行其他操作。
  151. while(((1<<0)&W25Q64_ReadStatus()));
  152. }
  153. void W25Q64_EraseChip(void)
  154. {
  155. W25Q64_WriteEnable();//执行写使能指令,
  156. while(!((1<<1)&W25Q64_ReadStatus()));//读取状态寄存器,判断WEL位,判断是否已经进入“写使能状态”
  157. FLSH_CS_L;//拉低片选
  158. SPI1_SendReveiveData(0xC7);//3)发送“芯片擦除指令”→0xC7
  159. FLSH_CS_H;//拉高片选
  160. //读取状态寄存器,判断“擦除动作”是否忙了。只有忙完“擦除”之后,才可以去执行其他操作。
  161. while(((1<<0)&W25Q64_ReadStatus()));
  162. }
  163. u8 W25Q64_WritePage(u32 addr,u8 *pData,u16 count)
  164. {
  165. if(count>256)
  166. {
  167. return 1;
  168. }
  169. W25Q64_WriteEnable();//执行写使能指令,
  170. while(!((1<<1)&W25Q64_ReadStatus()));//读取状态寄存器,判断WEL位,判断是否已经进入“写使能状态”
  171. FLSH_CS_L;//拉低片选
  172. SPI1_SendReveiveData(0x02);//发送“页编程指令”→0x02
  173. //MOSI发送要首地址,注意:高位先发送。
  174. SPI1_SendReveiveData((addr>>16)&0xFF);
  175. SPI1_SendReveiveData((addr>>8)&0xFF);
  176. SPI1_SendReveiveData(addr&0xFF);
  177. //MOSI发送数据。W25Q64内部地址自动递增。发送多个字节的数据需要通过循环来完成。
  178. while(count--)
  179. {
  180. SPI1_SendReveiveData(*pData);
  181. pData++;
  182. }
  183. FLSH_CS_H;//拉高片选
  184. //读取状态寄存器,判断“页编程动作”是否忙了。只有忙完“页编程”之后,才可以去执行其他操作。
  185. while(((1<<0)&W25Q64_ReadStatus()));
  186. return 0;
  187. }
  188. int main(void)
  189. {u8 writeBuf[ ] = {"欢度中秋"};
  190. u8 readBuf[50] ={0};
  191. u16 ID = 0x0000;
  192. SPI1_Init();
  193. usart1_Init();//初始化串口
  194. ID = W25Q64_ReadID();
  195. printf("W25Q64的ID号是0x%x\r\n",ID);
  196. W25Q64_EraseSector(0x000000);
  197. W25Q64_WritePage(0x000000,writeBuf,sizeof(writeBuf));
  198. printf("写入到W25Q64的数据是 →%s\r\n",writeBuf);
  199. W25Q64_ReadData(0x000000,readBuf,sizeof(writeBuf)) ;
  200. printf("从W25Q64的读取到数据是 →%s\r\n",readBuf);
  201. while(1)
  202. {
  203. }
  204. }

库函数版

  1. #include "stm32f4xx.h"
  2. #include "stdio.h"
  3. #define FLSH_CS_H (GPIOB->ODR |= 1<<14)
  4. #define FLSH_CS_L (GPIOB->ODR &=~(1<<14))
  5. void Delay_us(u16 us)
  6. {
  7. SysTick->CTRL &=~(1<<2); //选择时钟源为21MHZ
  8. SysTick->CTRL &=~(1<<1); //禁止滴答中断
  9. SysTick->LOAD = 21*us; //设置重装载寄存器的值
  10. SysTick->CTRL |= (1<<0); //使能滴答定时器
  11. while(!(SysTick->CTRL&(1<<16)));//阻塞判断定时时间是否到达,判断SysTick->CTRL的位
  12. }
  13. /******************** 串口打印函数 ***************************************/
  14. /* fputc是printf最底层的调用函数 */
  15. int fputc(int data,FILE *file)
  16. {
  17. while( !(USART1->SR & (1 << 6)) );//等待发送完成
  18. USART1->DR = data; //发送数据
  19. return data;
  20. }
  21. void usart1_Init()
  22. {
  23. u32 baudRate = 115200;
  24. GPIO_InitTypeDef GPIO_InitStructe;
  25. USART_InitTypeDef USART_InitStructe;
  26. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能 A 端口
  27. RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //使能串口端口
  28. GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIO 端口映射到 USART
  29. GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIO 端口映射到 USART
  30. // USART_DeInit(USART1); //复位
  31. GPIO_InitStructe.GPIO_Mode =GPIO_Mode_AF; //配置为输出模式
  32. GPIO_InitStructe.GPIO_Pin =GPIO_Pin_9|GPIO_Pin_10; //初始化 GPIOA9/10;
  33. GPIO_Init(GPIOA,&GPIO_InitStructe);
  34. USART_InitStructe.USART_BaudRate =baudRate; //波特率
  35. USART_InitStructe.USART_HardwareFlowControl =USART_HardwareFlowControl_None;//无硬件流控制
  36. USART_InitStructe.USART_Mode =USART_Mode_Tx|USART_Mode_Rx; //接收模式,发送模式使能
  37. USART_InitStructe.USART_Parity =USART_Parity_No; //无奇偶校验
  38. USART_InitStructe.USART_StopBits =USART_StopBits_1; //一位停止位
  39. USART_InitStructe.USART_WordLength =USART_WordLength_8b;//8 位数据位
  40. USART_Init(USART1,&USART_InitStructe);
  41. USART_Cmd(USART1,ENABLE); //使能串口
  42. }
  43. //FLASH_CS →PB14
  44. //SPI1_SCK →PB3
  45. //SPI1_MISO →PB4
  46. //SPI1_MOSI →PB5
  47. void SPI1_Init(void)
  48. {
  49. GPIO_InitTypeDef GPIO_InitStructure;
  50. SPI_InitTypeDef SPI_InitStructure;
  51. GPIO_InitTypeDef SPI_GPIO_InitStruct;
  52. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能 GPIOB 时钟
  53. RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//使能 SPI1 时钟
  54. //GPIOFB3,4,5 初始化设置
  55. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//PB3~5 复用功能输出
  56. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
  57. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  58. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  59. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  60. GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
  61. GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3 复用为 SPI1
  62. GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB4 复用为 SPI1
  63. GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB5 复用为 SPI1
  64. SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置 SPI 单向或者双向的数据模式:SPI 设置为双线双向全双工
  65. SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置 SPI 工作模式:设置为主 SPI
  66. SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置 SPI 的数据大小:SPI 发送接收8 位帧结构
  67. SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
  68. SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
  69. SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS 信号由硬件(NSS 管脚)还是软件(使用SSI 位)管理:内部 NSS 信号有 SSI 位控制
  70. SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; //定义波特率预分频的值:波特率预分频值为 32
  71. SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从 MSB 位还是 LSB 位开始:数据传输从 MSB 位开始
  72. SPI_Init(SPI1, &SPI_InitStructure); //根据 SPI_InitStruct 中指定的参数初始化外设 SPIx 寄存器
  73. SPI_Cmd(SPI1, ENABLE); //使能 SPI 外设
  74. SPI_GPIO_InitStruct.GPIO_Mode=GPIO_Mode_OUT;
  75. SPI_GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
  76. SPI_GPIO_InitStruct.GPIO_Pin=GPIO_Pin_14;
  77. SPI_GPIO_InitStruct.GPIO_Speed=GPIO_Fast_Speed;
  78. GPIO_Init(GPIOB,&SPI_GPIO_InitStruct);
  79. GPIO_SetBits(GPIOB,GPIO_Pin_14);
  80. }
  81. //data:待发送的数据
  82. //返回值:是接收到的数据
  83. u8 SPI1_SendReveiveData(u8 data)
  84. {
  85. while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
  86. SPI_I2S_SendData(SPI1, data);
  87. while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
  88. return SPI_I2S_ReceiveData(SPI1);
  89. }
  90. //返回值:厂商号+ID号 0xEF16
  91. u16 W25Q64_ReadID(void)
  92. {
  93. u8 mID = 0; //厂商号
  94. u8 dID = 0; //设备号
  95. FLSH_CS_L;//拉低片选:表示选择
  96. SPI1_SendReveiveData(0x90);//发送指令代码“0x90
  97. SPI1_SendReveiveData(0x00);//发送地址“0x00” ,发送最高的字节
  98. SPI1_SendReveiveData(0x00);//发送地址“0x00” ,发送中间的字节
  99. SPI1_SendReveiveData(0x00);//发送地址“0x00” ,发送最低的字节
  100. mID = SPI1_SendReveiveData(0xFF);//读厂商号,0xEF
  101. dID = SPI1_SendReveiveData(0xFF);//读取ID号,0x16
  102. FLSH_CS_H;//拉高片选:表示取消选择
  103. return mID<<8|dID;//返回厂商号+ID号 0xEF16
  104. }
  105. u8 W25Q64_ReadStatus(void)
  106. {
  107. u8 status = 0;
  108. FLSH_CS_L;//片选拉低
  109. SPI1_SendReveiveData(0x05);//STM32发送“读状态寄存器指令”→0x05给W25Q64
  110. status = SPI1_SendReveiveData(0xFF);//STM32读取状态寄存器
  111. FLSH_CS_H;//片选管脚拉高
  112. return status;
  113. }
  114. void W25Q64_WriteEnable(void)
  115. {
  116. FLSH_CS_L;//片选拉低
  117. SPI1_SendReveiveData(0x06);//STM32发送“写使能指令”→0x06给W25Q64
  118. FLSH_CS_H;//片选管脚拉高
  119. }
  120. //addr:地址
  121. //pBuf:读数据的存储区
  122. //count :要读多少给字节
  123. void W25Q64_ReadData(u32 addr,u8*pBuf,u32 count)
  124. {
  125. FLSH_CS_L;//拉低片选
  126. SPI1_SendReveiveData(0x03);//发送“读数据指令”→0x03
  127. //MOSI发送要读取的首地址,注意:高位先发送。
  128. SPI1_SendReveiveData((addr>>16)&0xFF);
  129. SPI1_SendReveiveData((addr>>8)&0xFF);
  130. SPI1_SendReveiveData(addr&0xFF);
  131. //MISO可以读取到数据,可以连续读,W25Q64内部地址会自动递增。
  132. while(count--)
  133. {
  134. *pBuf = SPI1_SendReveiveData(0xFF);
  135. pBuf++;
  136. }
  137. FLSH_CS_H;//拉高片选
  138. }
  139. void W25Q64_EraseSector(u32 addr)
  140. {
  141. W25Q64_WriteEnable();//执行写使能指令,
  142. while(!((1<<1)&W25Q64_ReadStatus()));//读取状态寄存器,判断WEL位,判断是否已经进入“写使能状态”
  143. FLSH_CS_L;//拉低片选
  144. SPI1_SendReveiveData(0x20);//发送“扇区擦除指令”→0x20
  145. //MOSI发送要扇区的首地址,注意:高位先发送。
  146. SPI1_SendReveiveData((addr>>16)&0xFF);
  147. SPI1_SendReveiveData((addr>>8)&0xFF);
  148. SPI1_SendReveiveData(addr&0xFF);
  149. FLSH_CS_H;//拉高片选
  150. //读取状态寄存器,判断“擦除动作”是否忙了。只有忙完“擦除”之后,才可以去执行其他操作。
  151. while(((1<<0)&W25Q64_ReadStatus()));
  152. }
  153. void W25Q64_EraseChip(void)
  154. {
  155. W25Q64_WriteEnable();//执行写使能指令,
  156. while(!((1<<1)&W25Q64_ReadStatus()));//读取状态寄存器,判断WEL位,判断是否已经进入“写使能状态”
  157. FLSH_CS_L;//拉低片选
  158. SPI1_SendReveiveData(0xC7);//3)发送“芯片擦除指令”→0xC7
  159. FLSH_CS_H;//拉高片选
  160. //读取状态寄存器,判断“擦除动作”是否忙了。只有忙完“擦除”之后,才可以去执行其他操作。
  161. while(((1<<0)&W25Q64_ReadStatus()));
  162. }
  163. u8 W25Q64_WritePage(u32 addr,u8 *pData,u16 count)
  164. {
  165. if(count>256)
  166. {
  167. return 1;
  168. }
  169. W25Q64_WriteEnable();//执行写使能指令,
  170. while(!((1<<1)&W25Q64_ReadStatus()));//读取状态寄存器,判断WEL位,判断是否已经进入“写使能状态”
  171. FLSH_CS_L;//拉低片选
  172. SPI1_SendReveiveData(0x02);//发送“页编程指令”→0x02
  173. //MOSI发送要首地址,注意:高位先发送。
  174. SPI1_SendReveiveData((addr>>16)&0xFF);
  175. SPI1_SendReveiveData((addr>>8)&0xFF);
  176. SPI1_SendReveiveData(addr&0xFF);
  177. //MOSI发送数据。W25Q64内部地址自动递增。发送多个字节的数据需要通过循环来完成。
  178. while(count--)
  179. {
  180. SPI1_SendReveiveData(*pData);
  181. pData++;
  182. }
  183. FLSH_CS_H;//拉高片选
  184. //读取状态寄存器,判断“页编程动作”是否忙了。只有忙完“页编程”之后,才可以去执行其他操作。
  185. while(((1<<0)&W25Q64_ReadStatus()));
  186. return 0;
  187. }
  188. int main(void)
  189. {u8 writeBuf[ ] = {"欢度中秋"};
  190. u8 readBuf[50] ={0};
  191. u16 ID = 0x0000;
  192. SPI1_Init();
  193. usart1_Init();//初始化串口
  194. ID = W25Q64_ReadID();
  195. printf("W25Q64的ID号是0x%x\r\n",ID);
  196. W25Q64_EraseSector(0x000000);
  197. W25Q64_WritePage(0x000000,writeBuf,sizeof(writeBuf));
  198. printf("写入到W25Q64的数据是 →%s\r\n",writeBuf);
  199. W25Q64_ReadData(0x000000,readBuf,sizeof(writeBuf)) ;
  200. printf("从W25Q64的读取到数据是 →%s\r\n",readBuf);
  201. while(1)
  202. {
  203. }
  204. }

 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号