当前位置:   article > 正文

STM32实现软件SPI对W25Q64内存芯片实现读写操作_w25q64写入失败

w25q64写入失败

先看看本次实验的成果吧:

这么简单的一个程序,我学习了一个星期左右,终于把所有的关节都打通了。所有代码都能什么都不看背着敲出来了。为了使自己的记忆更为清晰,特意总结了一个思维导图,感觉自己即便是日后忘记了看一遍思维导图也就知道怎么写了。特此展示一下吧!

STM32内部集成了硬件SPI收发电路,

可以由硬件自动执行时钟生成、数据收发等功能,

减轻CPU的负担 可配置8位/16位数据帧、高位先行/低位先行

时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256) 支持多主机模型、主或从操作

可精简为半双工/单工通信 支持DMA 兼容I2S协议

STM32F103C8T6 硬件SPI资源:SPI1、SPI2

这个图应该是从右往左看才对的,我个人感觉是这个样子的,因为我写程序是这个顺序的,下面我就把所有的程序展示一下吧:

根据时序图会更好更快的理解程序的逻辑,一切都是要最后芯片的时序来编程,否则就不能和芯片通讯了。

首先是SPI.c的文件:

  1. #include "stm32f10x.h" // Device header
  2. #define GPIOX GPIOA //宏定义GPIOA,需要更改端口时只更改这里就好了
  3. #define RCC_APB2Periph_GPIOX RCC_APB2Periph_GPIOA //宏定义时钟开启端口
  4. #define MySPI_CS GPIO_Pin_4 //宏定义CS片选信号
  5. #define MySPI_MOSI GPIO_Pin_7 //宏定义MOSI主机输出信号
  6. #define MySPI_CLK GPIO_Pin_5 //宏定义时钟信号
  7. #define MySPI_MISO GPIO_Pin_6 //宏定义主机输入信号
  8. void MySPI_W_CS(uint8_t BitValue) //位操作片选信号
  9. {
  10. GPIO_WriteBit(GPIOX, MySPI_CS, (BitAction)BitValue);
  11. }
  12. void MySPI_W_MOSI(uint8_t BitValue) //位操作主机输出信号
  13. {
  14. GPIO_WriteBit(GPIOX, MySPI_MOSI, (BitAction)BitValue);
  15. }
  16. void MySPI_W_CLK(uint8_t BitValue) //位操作时钟信号
  17. {
  18. GPIO_WriteBit(GPIOX, MySPI_CLK, (BitAction)BitValue);
  19. }
  20. uint8_t MySPI_Read_MISO(void) //读取主机输入信号
  21. {
  22. return GPIO_ReadInputDataBit(GPIOX, MySPI_MISO);
  23. }
  24. void MySPI_Init(void) //SPI初始化
  25. {
  26. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOX, ENABLE); //开启时钟
  27. GPIO_InitTypeDef GPIO_InitStruct;
  28. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
  29. GPIO_InitStruct.GPIO_Pin = MySPI_CS | MySPI_MOSI | MySPI_CLK;
  30. GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  31. GPIO_Init(GPIOX, &GPIO_InitStruct); //初始化片选,主机输出,时钟三个信号为推挽输出模式
  32. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
  33. GPIO_InitStruct.GPIO_Pin = MySPI_MISO;
  34. GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  35. GPIO_Init(GPIOX, &GPIO_InitStruct); //初始化主机输入信号为上拉输入模式
  36. MySPI_W_CS(1); //片选信号高电平,没有选中从机
  37. MySPI_W_CLK(0); //时钟低电平
  38. }
  39. void MySPI_Start(void) //SPI开始函数
  40. {
  41. MySPI_W_CS(0); // 片选信号低电平,选中从机
  42. }
  43. void MySPI_Stop(void) //SPI结束函数
  44. {
  45. MySPI_W_CS(1); //片选信号高电平,没有选中从机
  46. }
  47. uint8_t MySPI_RW_Byte(uint8_t SendByte) //SPI读写一个字节函数
  48. {
  49. uint8_t i;
  50. for(i = 0; i < 8; i ++) //循环8次,一个字节8位,每次操作1位
  51. {
  52. MySPI_W_MOSI(SendByte & 0x80); // 主机输出信号写:发送数据的最高位
  53. SendByte <<= 1; // 发送信号右移1位,下次循环到来时还是发送最高位
  54. MySPI_W_CLK(1); //时钟信号高电平
  55. if(MySPI_Read_MISO() == 1){SendByte |= 0x01;} // 如果接收到的从机信号为1:发送数据的最低位就写入1
  56. MySPI_W_CLK(0); //时钟信号低电平
  57. }
  58. return SendByte; //返回发送的数据:此时经过了八次从最低位往最高位写入读入的位值,此时就是收到的新的一个字节数据。
  59. }

接着是SPI.h的文件:

  1. #ifndef __MYSPI_H
  2. #define __MYSPI_H
  3. void MySPI_Init(void); //SPI初始化
  4. void MySPI_Start(void); //SPI开始
  5. void MySPI_Stop(void); //SPI结束
  6. uint8_t MySPI_RW_Byte(uint8_t SendByte); //SPI读写一个字节
  7. #endif

写入操作时:

写入操作前,必须先进行写使能 每个数据位只能由1改写为0,不能由0改写为1

写入数据前必须先擦除,擦除后,所有数据位变为1 擦除必须按最小擦除单元进行

连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入

写入操作结束后,芯片进入忙状态,不响应新的读写操作

读取操作时:

直接调用读取时序,无需使能,无需额外操作,没有页的限制,

读取操作结束后不会进入忙状态,但不能在忙状态时读取

接着是W25Q64.c的文件:

  1. #include "stm32f10x.h" // Device header
  2. #include "MySPI.h"
  3. #include "W25Q64_Int.h"
  4. void W25Q64_Init(void) //W25Q64初始化
  5. {
  6. MySPI_Init(); //SPI初始化
  7. }
  8. void W25Q64_Read_ID(uint8_t *MID, uint16_t *DID) //W25Q64 读取ID号
  9. {
  10. MySPI_Start(); //SPI开始
  11. MySPI_RW_Byte(W25Q64_JEDEC_ID); //SPI写命令
  12. *MID = MySPI_RW_Byte(W25Q64_DUMMY_BYTE); //SPI读取从机发过来的第一个字节就是MID号
  13. *DID = MySPI_RW_Byte(W25Q64_DUMMY_BYTE); //SPI读取从机发过来的第二个字节就是DID的高8位
  14. *DID <<= 8; //上面读到的是高8位所以向右移动8位就是高8位了
  15. *DID |= MySPI_RW_Byte(W25Q64_DUMMY_BYTE); //低8位再写入从机发过来的第三个字节
  16. MySPI_Stop(); //SPI停止
  17. }
  18. void W25Q64_WaitBusy(void) //W25Q64 等待忙函数
  19. {
  20. uint32_t Timeout = 100000; //定义一个超时变量
  21. MySPI_Start(); //SPI开始
  22. MySPI_RW_Byte(W25Q64_READ_STATUS_REGISTER_1); //读取忙标志位命令
  23. while((MySPI_RW_Byte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01) //如果读取到的标志位最后1为是1就循环
  24. {
  25. Timeout --; //超时变量自减
  26. if(Timeout == 0) //如果超时变量为0了,while循环还没有结束
  27. {
  28. MySPI_Stop(); //SPI停止
  29. break; //退出while循环(防止在While循环中出不去)
  30. }
  31. }
  32. MySPI_Stop(); //SPI停止
  33. }
  34. void W25Q64_WriteENABLE(void) //W25Q64写使能
  35. {
  36. MySPI_Start(); //SPI开始
  37. MySPI_RW_Byte(W25Q64_WRITE_ENABLE); //W25Q64写使能命令
  38. MySPI_Stop(); //SPI停止
  39. }
  40. void W25Q64_Write_Data(uint32_t Address, uint8_t *DataArry, uint16_t Len) //W25Q64写数据(参数:32位的地址,8位的数组,要写入数据的长度)
  41. {
  42. uint16_t i; //定义16位的i,循环用,一页可以写256个字节,8位的画最大255,差一个不够,所以用16位的
  43. W25Q64_WriteENABLE(); //W25Q64写使能(W25Q64需要写入数据前的必须操作)
  44. MySPI_Start(); //SPI开始
  45. MySPI_RW_Byte(W25Q64_PAGE_PROGRAM); //SPI写入操作页的命令
  46. MySPI_RW_Byte(Address >> 16); //SPI写入24位地址的高8位
  47. MySPI_RW_Byte(Address >> 8); //SPI写入24位地址的中间8位
  48. MySPI_RW_Byte(Address); //SPI写入24位地址的低8位
  49. for(i = 0; i < Len; i ++) //循环 写入数据的长度 次
  50. {
  51. MySPI_RW_Byte(DataArry[i]); //SPI写入 写入数组的(从低到高)单个字节
  52. }
  53. MySPI_Stop(); //SPI停止
  54. W25Q64_WaitBusy(); //W25Q64等待忙完
  55. }
  56. void W25Q64_SectorErase(uint32_t Address) // W25Q64擦除数据
  57. {
  58. W25Q64_WriteENABLE(); //W25Q64写使能(W25Q64需要写入数据前的必须操作)
  59. MySPI_Start(); //SPI开始
  60. MySPI_RW_Byte(W25Q64_SECTOR_ERASE_4KB); //SPI写入擦除页的命令
  61. MySPI_RW_Byte(Address >> 16); //SPI写入24位地址的高8位
  62. MySPI_RW_Byte(Address >> 8); //SPI写入24位地址的中间8位
  63. MySPI_RW_Byte(Address); //SPI写入24位地址的低8位
  64. MySPI_Stop(); //SPI停止
  65. W25Q64_WaitBusy(); //W25Q64等待忙完
  66. }
  67. void W25Q64_ReadData(uint32_t Address, uint8_t *DataArry, uint32_t Len) // W25Q64读取数据
  68. {
  69. uint32_t i; // 定义读取数据的长度变量(读取不受页的空间限制,读多少地址都会自增) 所以i的容量要大
  70. MySPI_Start(); //SPI开始
  71. MySPI_RW_Byte(W25Q64_READ_DATA); //SPI写入读取的命令
  72. MySPI_RW_Byte(Address >> 16); //SPI写入24位地址的高8位
  73. MySPI_RW_Byte(Address >> 8); //SPI写入24位地址的中间8位
  74. MySPI_RW_Byte(Address); //SPI写入24位地址的低8位
  75. for(i = 0; i < Len; i ++) //循环 要读取的字节数 次
  76. {
  77. DataArry[i] = MySPI_RW_Byte(W25Q64_DUMMY_BYTE); //数组的第i位赋值为 W25Q64传过来的字节
  78. }
  79. MySPI_Stop(); //SPI停止
  80. } //由于读完自动清除忙标志位,所以读完不用等待忙完

然后是W25Q64.h的文件:

  1. #ifndef __W25Q64_H
  2. #define __W25Q64_H
  3. void W25Q64_Init(void);
  4. void W25Q64_Read_ID(uint8_t *MID, uint16_t *DID);
  5. void W25Q64_Write_Data(uint32_t Address, uint8_t *DataArry, uint16_t Len);
  6. void W25Q64_SectorErase(uint32_t Address);
  7. void W25Q64_ReadData(uint32_t Address, uint8_t *DataArry, uint16_t Len);
  8. #endif

最后就是main.c的主函数文件了:

  1. #include "stm32f10x.h" // Device header
  2. #include "OLED.h"
  3. #include "W25Q64.h"
  4. uint8_t MID;
  5. uint16_t DID;
  6. uint8_t DataW[] = {0x55, 0x66, 0x77, 0x88};
  7. uint8_t DataR[4];
  8. int main(void)
  9. {
  10. OLED_Init(); //oled 屏幕初始化
  11. W25Q64_Init(); //W25Q64初始化
  12. OLED_ShowString(1,1, "MID: DID:"); //OLED第一行第一列显示字符串
  13. W25Q64_Read_ID(&MID, &DID); // W25Q64 读ID
  14. OLED_ShowHexNum(1,5, MID, 2); //OLED显示MID
  15. OLED_ShowHexNum(1,13, DID, 4); //OLED显示DID
  16. OLED_ShowString(2,1, "W:"); // OLED显示要写入的数据
  17. OLED_ShowString(3,1, "R:"); // OLED显示读出的数据
  18. W25Q64_SectorErase(0x000100); //W25Q64擦除地址为0x000100开始的一页的数据
  19. W25Q64_Write_Data(0x000100, DataW, 4); //W25Q64从地址为0x000100的地方开始写入数据,共写入4个字节
  20. W25Q64_ReadData(0x000100, DataR, 4); //W25Q64从地址为0x000100的地方开始读数据,共读出4个字节的数据
  21. OLED_ShowHexNum(3,3, DataR[0], 2); //OLED显示读出的数据0
  22. OLED_ShowHexNum(3,6, DataR[1], 2); //OLED显示读出的数据1
  23. OLED_ShowHexNum(3,9, DataR[2], 2); //OLED显示读出的数据2
  24. OLED_ShowHexNum(3,12, DataR[3], 2); //OLED显示读出的数据3
  25. OLED_ShowHexNum(2,3, DataW[0], 2); //OLED显示要写入的数据0
  26. OLED_ShowHexNum(2,6, DataW[1], 2); //OLED显示要写入的数据1
  27. OLED_ShowHexNum(2,9, DataW[2], 2); //OLED显示要写入的数据2
  28. OLED_ShowHexNum(2,12, DataW[3], 2); //OLED显示要写入的数据3
  29. while(1)
  30. {
  31. }
  32. }

还有一个W25Q64芯片的命令文件W25Q64_Int.h:里面放的是写好的命令和值的对应关系都做好了宏定义:

  1. #ifndef __W25Q64_INT_H
  2. #define __W25Q64_INT_H
  3. #define W25Q64_WRITE_ENABLE 0x06 //写使能命令值
  4. #define W25Q64_WRITE_DISABLE 0x04 //写失能命令值
  5. #define W25Q64_READ_STATUS_REGISTER_1 0x05 //读芯片忙状态寄存器,最后一位为1,代表忙
  6. #define W25Q64_READ_STATUS_REGISTER_2 0x35
  7. #define W25Q64_WRITE_STATUS_REGISTER 0x01
  8. #define W25Q64_PAGE_PROGRAM 0x02 //页写入命令值
  9. #define W25Q64_QUAD_PAGE_PROGRAM 0x32
  10. #define W25Q64_BLOCK_ERASE_64KB 0xD8
  11. #define W25Q64_BLOCK_ERASE_32KB 0x52
  12. #define W25Q64_SECTOR_ERASE_4KB 0x20 //擦除4KB命令
  13. #define W25Q64_CHIP_ERASE 0xC7
  14. #define W25Q64_ERASE_SUSPEND 0x75
  15. #define W25Q64_ERASE_RESUME 0x7A
  16. #define W25Q64_POWER_DOWN 0xB9
  17. #define W25Q64_HIGH_PERFORMANCE_MODE 0xA3
  18. #define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF
  19. #define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB
  20. #define W25Q64_MANUFACTURER_DEVICE_ID 0x90
  21. #define W25Q64_READ_UNIQUE_ID 0x4B
  22. #define W25Q64_JEDEC_ID 0x9F //读ID命令
  23. #define W25Q64_READ_DATA 0x03 //读数据命令
  24. #define W25Q64_FAST_READ 0x0B
  25. #define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B
  26. #define W25Q64_FAST_READ_DUAL_IO 0xBB
  27. #define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B
  28. #define W25Q64_FAST_READ_QUAD_IO 0xEB
  29. #define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3
  30. #define W25Q64_DUMMY_BYTE 0xFF // 置换一字节的数据命令
  31. #endif

然后根据我的文件架构来布置代码编译后就能实现对W25Q64存储芯片的读写了:

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号