纬图虚拟仪器论坛

 找回密码
 立即注册
搜索
查看: 15833|回复: 4

利用STM32的SPI读写W25X16系列芯片程序说明及下载

[复制链接]
发表于 2012-12-6 10:04:52 | 显示全部楼层 |阅读模式
第一步:配置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.        
  21.         SPI_GPIO_Configuration(SPIx);

  22.         SPI_SSOutputCmd(SPIx, ENABLE);
  23.         SPI_Cmd(SPIx, ENABLE);
  24. }
复制代码
注意,在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是正确的。
最后再上传一个实现数据擦除、写、读的测试程序,程序运行效果如下:
w25x20.jpg
W25X16.jpg
程序源码下载: spi_W25X10.rar (626.02 KB, 下载次数: 501)
回复

使用道具 举报

 楼主| 发表于 2012-12-7 09:40:31 | 显示全部楼层
wkxwkx101 发表于 2012-12-6 22:13
希望看你写的程序  风格很不错    只是有个问题 MISO为什么要配置为推挽输出模式?

程序帖出来了格式有点乱,其实GPIO_Mode_AF_PP不是推挽输出,是复用功能输出,也就是说这个管脚不是用来作为普通的IO口用的,而是其他功能,所以虽然MISO引脚应该为输入引脚,但是也不必初始化时设置为输入。
回复 支持 反对

使用道具 举报

发表于 2012-12-11 16:18:36 | 显示全部楼层
谢谢分享。。。。。。。。。。。。
回复 支持 反对

使用道具 举报

发表于 2013-2-19 23:20:39 | 显示全部楼层
谢谢分享。。
回复 支持 反对

使用道具 举报

发表于 2013-3-5 23:26:53 | 显示全部楼层
啥也不说了,楼主就是给力!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|纬图虚拟仪器

GMT+8, 2024-12-22 12:03 , Processed in 0.081991 second(s), 20 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表