[原创] 利用STM32的SPI读写W25X16系列芯片程序说明

  • 程序
  • yanli
  • LV3工程师
  • |      2017-05-18 17:50:50
  • 浏览量 1210
  • 回复:0
第一步:配置SPI SPI的配置分3步,分别如下: 1、使能SPI时钟和所用到的引脚时钟。程序如下所示。
  1. /**
  2. * @brief 使能SPI时钟
  3. * @param SPIx 需要使用的SPI
  4. * @retval None
  5. */
  6. static void SPI_RCC_Configuration(SPI_TypeDef* SPIx)
  7. {
  8. if(SPIx==SPI1){
  9. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_SPI1,ENABLE);
  10. }else{
  11. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
  12. RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
  13. }
  14. }
复制代码
2、配置SPI所用到的相关引脚,程序如下所示。
  1. /**
  2. * @brief 配置指定SPI的引脚
  3. * @param SPIx 需要使用的SPI
  4. * @retval None
  5. */
  6. static void SPI_GPIO_Configuration(SPI_TypeDef* SPIx)
  7. {
  8. GPIO_InitTypeDef GPIO_InitStruct;
  9. if(SPIx==SPI1){
  10. GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6|GPIO_Pin_7;
  11. GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  12. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
  13. GPIO_Init(GPIOA, &GPIO_InitStruct);
  14. //初始化片选输出引脚
  15. GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
  16. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
  17. GPIO_Init(GPIOA, &GPIO_InitStruct);
  18. GPIO_SetBits(GPIOA,GPIO_Pin_4);
  19. }else{
  20. GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14|GPIO_Pin_15;
  21. GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  22. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
  23. GPIO_Init(GPIOB, &GPIO_InitStruct);
  24. //初始化片选输出引脚
  25. GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
  26. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
  27. GPIO_Init(GPIOB, &GPIO_InitStruct);
  28. GPIO_SetBits(GPIOB,GPIO_Pin_12);
  29. }
  30. }
复制代码
3、根据W25X16数据传输时序配置SPI,程序如下所示。
  1. /**
  2. * @brief 根据外部SPI设备配置SPI相关参数
  3. * @param SPIx 需要使用的SPI
  4. * @retval None
  5. */
  6. void SPI_Configuration(SPI_TypeDef* SPIx)
  7. {
  8. SPI_InitTypeDef SPI_InitStruct;
  9. SPI_RCC_Configuration(SPIx);
  10. SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32;
  11. SPI_InitStruct.SPI_Direction= SPI_Direction_2Lines_FullDuplex;
  12. SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
  13. SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
  14. SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
  15. SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
  16. SPI_InitStruct.SPI_NSS = SPI_NSS_Hard;
  17. SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
  18. SPI_InitStruct.SPI_CRCPolynomial = 7;
  19. SPI_Init(SPIx, &SPI_InitStruct);
  20. SPI_GPIO_Configuration(SPIx);
  21. SPI_SSOutputCmd(SPIx, ENABLE);
  22. SPI_Cmd(SPIx, ENABLE);
  23. }
复制代码
注意,在SPI的配置中,SPI_InitStruct.SPI_NSS我是用的SPI_NSS_Hard模式,所以必须调用SPI_SSOutputCmd(SPIx, ENABLE);函数,否则SPI不能正常工作。 第二步:实现底层的SPI读写函数 SPI实际上就是芯片内的两个移位寄存器,一个移位寄存器通过时钟将数据串行输入到芯片,另一个移位寄存器通过时钟将数据串行发送出去,所以在数据收或者发送的时候,接收和发送是同时发生的。 SPI的写数据函数实现,程序如下所示。
  1. /**
  2. * @brief 写1字节数据到SPI总线
  3. * @param SPIx 需要使用的SPI
  4. * @param TxData 写到总线的数据
  5. * @retval 数据发送状态
  6. * @arg 0 数据发送成功
  7. * @arg -1 数据发送失败
  8. */
  9. int32_t SPI_WriteByte(SPI_TypeDef* SPIx, uint16_t TxData)
  10. {
  11. uint8_t retry=0;
  12. while((SPIx->SR&SPI_I2S_FLAG_TXE)==0); //等待发送区空
  13. {
  14. retry++;
  15. if(retry>200)return -1;
  16. }
  17. SPIx->DR=TxData; //发送一个byte
  18. retry=0;
  19. while((SPIx->SR&SPI_I2S_FLAG_RXNE)==0); //等待接收完一个byte
  20. {
  21. retry++;
  22. if(retry>200)return -1;
  23. }
  24. SPIx->DR;
  25. return 0; //返回收到的数据
  26. }
复制代码
程序首先判断发送是否为空,若不为空则等待,同时查询次数加1,当查询次数到一定的次数后发送区还是不为空则返回一个错误代码,若发送区为空则将数据写到数据发送寄存器DR中,此时SPI会自动启动时钟并将数据串行移位输出,同时也将MISO信号线上的数据串行输入,由于W25X16是半双工的,所以读到的数据无意义,可以省略不要。 在这个程序中,等待收到一个数据是非常有必要的,如果没有加这条语句则可能出现数据还没有发送完毕,但是程序直接执行到后面讲片选信号给DISABLE了,这样从机就无法完整的接收到数据,所以在这里最好加上这个查询语句。 SPI的读数据函数实现,程序如下所示。
  1. /**
  2. * @brief 从SPI总线读取1字节数据
  3. * @param SPIx 需要使用的SPI
  4. * @param p_RxData 数据储存地址
  5. * @retval 数据读取状态
  6. * @arg 0 数据读取成功
  7. * @arg -1 数据读取失败
  8. */
  9. int32_t SPI_ReadByte(SPI_TypeDef* SPIx, uint16_t *p_RxData)
  10. {
  11. uint8_t retry=0;
  12. while((SPIx->SR&SPI_I2S_FLAG_TXE)==0); //等待发送区空
  13. {
  14. retry++;
  15. if(retry>200)return -1;
  16. }
  17. SPIx->DR=0xFF; //发送一个byte
  18. retry=0;
  19. while((SPIx->SR&SPI_I2S_FLAG_RXNE)==0); //等待接收完一个byte
  20. {
  21. retry++;
  22. if(retry>200)return -1;
  23. }
  24. *p_RxData = SPIx->DR;
  25. return 0; //返回收到的数据
  26. }
复制代码
读数据和写数据类似,由于SPI传输数据需要时钟来驱动移位寄存器使数据串行输入,所以必须先向发送数据寄存器写一个数据发送数据以产生数据传输需要的时钟,然后程序等待数据接收完毕,数据接收完后返回数据。 第三步:读W25X16的Device ID 完成了SPI底层读写函数后就可以操作外部的SPI存储器了,我们首先来读取器件的ID值。 在SPI数据传输的时候需要使能从设备,我们使用的是GPIOA.4作为设备选择信号线,所以我们做如下宏定义来实现使能了禁能从设备。
  1. #define W25X_ENABLE GPIO_ResetBits(GPIOA,GPIO_Pin_4)
  2. #define W25X_DISABLE GPIO_SetBits(GPIOA,GPIO_Pin_4)
复制代码
根据W25X16芯片手册我们定义如下的一些命令操作码。
  1. #define W25X_CMD_READ_STATUS 0x05
  2. #define W25X_CMD_READ_ID 0x90
  3. #define W25X_CMD_WRITE_ENABLE 0x06
  4. #define W25X_CMD_WRITE_DISABLE 0x04
  5. #define W25X_CMD_WRITE_STATUS 0x01
  6. #define W25X_CMD_READ_DATA 0x03
  7. #define W25X_CMD_FAST_READ_DATA 0x0B
  8. #define W25X_CMD_FAST_READ_DUAL 0x3B
  9. #define W25X_CMD_PAGE_PROGRAM 0x02
  10. #define W25X_CMD_BLOCK_ERASE 0xD8
  11. #define W25X_CMD_SECTOR_ERASE 0x20
  12. #define W25X_CMD_CHIP_ERASE 0xC7
  13. #define W25X_CMD_POWER_DOWN 0xB9
  14. #define W25X_CMD_REL_POWER_DOWN 0xAB
  15. #define W25X_CMD_DEVICE_ID 0xAB
  16. #define W25X_CMD_MANUFACT_DEVICE_ID 0x90
  17. #define W25X_CMD_JEDEC_DEVICE_ID 0x9F
复制代码
然后根据芯片读Device ID的时序实现设备ID的读取,程序如下。
  1. /**
  2. * @brief 读取W25X10芯片的ID
  3. * @param p_DeviceID 芯片ID将存入这个指针所指的变量中
  4. * @retval None
  5. */
  6. void W25X_ReadID(uint16_t *p_DeviceID)
  7. {
  8. uint16_t Temp;
  9. W25X_ENABLE;
  10. SPI_WriteByte(SPI1,W25X_CMD_READ_ID);
  11. SPI_WriteByte(SPI1,0x00);
  12. SPI_WriteByte(SPI1,0x00);
  13. SPI_WriteByte(SPI1,0x00);
  14. SPI_ReadByte(SPI1,&Temp);
  15. *p_DeviceID = Temp << 8;
  16. SPI_ReadByte(SPI1,&Temp);
  17. *p_DeviceID |= Temp;
  18. W25X_DISABLE;
  19. }
复制代码
读到的ID值保存在p_DeviceID指针所指的变量中,用printf("Device ID : 0x%04X\n\r",DeviceID);可以将数据通过串口打印输出。我是用的W25X20芯片测试的,得到的ID值为0xEF15,根据数据手册可知独到的ID是正确的。 最后再上传一个实现数据擦除、写、读的测试程序,程序运行效果如下:
  • 0
  • 收藏
  • 举报
  • 分享
我来回复

登录后可评论,请 登录注册

所有回答 数量:0
x
收藏成功!点击 我的收藏 查看收藏的全部帖子