查看: 1329|回复: 0

【青风带你学stm32f051系列教程】第12课 SPI读写串行FLASH

[复制链接]

该用户从未签到

发表于 2013-2-6 00:10:37 | 显示全部楼层 |阅读模式
分享到:
SPI(Serial Peripheral Interface--串行外设接口)总线系统是一种同步串行外设接口,它可以使MCU与各种外围设备以串行方式进行通信以交换信息。SPI有三个寄存器分别为:控制寄存器SPCR,状态寄存器SPSR,数据寄存器SPDR。外围设备包括FLASHRAM、网络控制器、LCD显示驱动器、A/D转换器和MCU等。SPI总线系统可直接与各个厂家生产的多种标准外围器件直接接口,该接口一般使用4条线:串行时钟线(SCLK)、主机输入/从机输出数据线MISO、主机输出/从机输入数据线MOSI和低电平有效的从机选择线SS(有的SPI接口芯片带有中断信号线INT、有的SPI接口芯片没有主机输出/从机输入数据线MOSI)。 本实验通过SPI读写串行FLASH ,串行FLASH采样W25X16。
硬件准备:
硬件配置入下图所示,在TFT转接板和SD卡共用一个SPI接口:

| PF5-CS : W25X16-CS |
| PB13-SPI2-SCK : W25X16-CLK |
| PB14-SPI2-MISO : W25X16-DO |
| PB15-SPI2-MOSI : W25X16-DIO |
CS:FLASH片选信号引脚。
SCK:FLASH时钟信号引脚。
MISO:FLASH主入从出引脚。
MOSI:FLASH主出从进引脚。
硬件按照如上方式连接后,下面来配置驱动程序。
软件配置:
采用库函数编写驱动,工程目录如下图所示,用户需要编写FALSH驱动函数w25x16.c驱动函数和主函数main.c.

下面我们首先来讨论w25x16.c的驱动编写。首先对FLASH进行初始化,包括初始化几个方面:
时钟设置, IO端口复用,SPI参数设置,
[c]
void SPI_FLASH_Init(void){
GPIO_InitTypeDef GPIO_InitStruct; SPI_InitTypeDef SPI_InitStruct;RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOF| RCC_AHBPeriph_GPIOB, ENABLE);//配置gpio时钟
RCC_APB1PeriphClockCmd(FLASH_SPI2, ENABLE); //配置spi时钟
GPIO_InitStruct.GPIO_Pin = FLASH_SCK_PIN;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3;GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;GPIO_Init(FLASH_SCK_PORT, &GPIO_InitStruct);//时钟gpio端口模式
/*!< Configure SPI pins: MISO */GPIO_InitStruct.GPIO_Pin = FLASH_MISO_PIN;GPIO_Init(FLASH_MISO_PORT, &GPIO_InitStruct);
/*!< Configure SPI pins: MOSI */GPIO_InitStruct.GPIO_Pin =FLASH_MOSI_PIN;GPIO_Init(FLASH_MOSI_PORT, &GPIO_InitStruct);
/* Connect PXx to SPI_SCK */GPIO_PinAFConfig(FLASH_SCK_PORT, FLASH_SCK_SOURCE, FLASH_SCK_AF);
/* Connect PXx to SPI_MISO */GPIO_PinAFConfig(FLASH_MISO_PORT, FLASH_MISO_SOURCE, FLASH_MISO_AF);
/* Connect PXx to SPI_MOSI */GPIO_PinAFConfig(FLASH_MOSI_PORT, FLASH_MOSI_SOURCE, FLASH_MOSI_AF); //设置gpio端口的复用
GPIO_InitStruct.GPIO_Pin =FLASH_CS_PIN; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3; GPIO_Init(FLASH_CS_PORT, &GPIO_InitStruct);
SPI_FLASH_CS_HIGH();
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//配置spi方向 SPI_InitStruct.SPI_Mode = SPI_Mode_Master;//配置spi模式 SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;//配置数据格式 SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;//配置时钟高电平稳态 SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;//配置时钟bit位捕获方式SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;//设置nss管脚软件管理SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;//设置spi波特率分频值SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;//指定数据传输从msb位开始SPI_InitStruct.SPI_CRCPolynomial = 7;//指定用于CRC计算的值SPI_Init(SPI2, &SPI_InitStruct);//调入结构体SPI_RxFIFOThresholdConfig(SPI2, SPI_RxFIFOThreshold_QF);//设置接收缓冲SPI_Cmd(SPI2, ENABLE); /*!< SD_SPI enable */ }
 
[/c]
在stm32f0xx_spi.h文件中设置了spi参数结构体,如下代码所示,初始化的时候直接进行调用:
[c]
typedef struct{ uint16_t SPI_Direction; uint16_t SPI_Mode; uint16_t SPI_DataSize; uint16_t SPI_CPOL; uint16_t SPI_CPHA; uint16_t SPI_NSS; uint16_t SPI_BaudRatePrescaler; uint16_t SPI_FirstBit; uint16_t SPI_CRCPolynomial;}SPI_InitTypeDef;//spi参数结构体
 
[/c]
初始化后,开始编写 读和写W25X16的代码,时序关系我们需要参考w25x16的数据手册:
首先通过SPI接口发送字节,同时接收:
[c]
uint8_t SPI_FLASH_SendByte(uint8_t byte){ while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);//判断是否发送完成 SPI_SendData8(SPI2, byte);//SPI发送字节
/* Wait to receive a byte */ while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET);//是否已经读取 /* Return the byte read from the SPI bus */ return SPI_ReceiveData8(SPI2);//SPI接收}
 
[/c]
上面的发送命令接收数据是基本操作步骤,下面写读取器件ID代码,参考w25x16代码参考手册中的时序图:

[c]
uint32_t SPI_FLASH_ReadDeviceID(void){ uint32_t Temp = 0;
/* Select the FLASH: Chip Select low */ SPI_FLASH_CS_LOW();
/* Send "RDID " instruction */ SPI_FLASH_SendByte(W25X_DeviceID); SPI_FLASH_SendByte(Dummy_Byte); SPI_FLASH_SendByte(Dummy_Byte); SPI_FLASH_SendByte(Dummy_Byte);
/* Read a byte from the FLASH */ Temp = SPI_FLASH_SendByte(Dummy_Byte);
/* Deselect the FLASH: Chip Select high */ SPI_FLASH_CS_HIGH();
return Temp;}
 
[/c]
读取制造ID参考时序图:

[c]
uint32_t SPI_FLASH_ReadID(void){ uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
/* Select the FLASH: Chip Select low */ SPI_FLASH_CS_LOW();
/* Send "RDID " instruction */ SPI_FLASH_SendByte(W25X_JedecDeviceID);
/* Read a byte from the FLASH */ Temp0 = SPI_FLASH_SendByte(Dummy_Byte);
/* Read a byte from the FLASH */ Temp1 = SPI_FLASH_SendByte(Dummy_Byte);
/* Read a byte from the FLASH */ Temp2 = SPI_FLASH_SendByte(Dummy_Byte);
/* Deselect the FLASH: Chip Select high */ SPI_FLASH_CS_HIGH();
Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
return Temp;}
 
[/c]
W25X16页写参考时序图:

[c]
void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite){ /* Enable the write access to the FLASH */ SPI_FLASH_WriteEnable();
/* Select the FLASH: Chip Select low */ SPI_FLASH_CS_LOW(); /* Send "Write to Memory " instruction */ SPI_FLASH_SendByte(W25X_PageProgram); /* Send WriteAddr high nibble address byte to write to */ SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16); /* Send WriteAddr medium nibble address byte to write to */ SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8); /* Send WriteAddr low nibble address byte to write to */ SPI_FLASH_SendByte(WriteAddr & 0xFF);
if(NumByteToWrite > SPI_FLASH_PerWritePageSize) { NumByteToWrite = SPI_FLASH_PerWritePageSize; //printf("
Err: SPI_FLASH_PageWrite too large!"); }
/* while there is data to be written on the FLASH */ while (NumByteToWrite--) { /* Send the current byte */ SPI_FLASH_SendByte(*pBuffer); /* Point on the next byte to be written */ pBuffer++; }
/* Deselect the FLASH: Chip Select high */ SPI_FLASH_CS_HIGH(); /* Wait the end of Flash writing */ SPI_FLASH_WaitForWriteEnd();}
 
[/c]
W25x16扇区擦除时序图:

[c]
void SPI_FLASH_SectorErase(uint32_t SectorAddr){ /* Send write enable instruction */ SPI_FLASH_WriteEnable(); SPI_FLASH_WaitForWriteEnd(); /* Sector Erase */ /* Select the FLASH: Chip Select low */ SPI_FLASH_CS_LOW(); /* Send Sector Erase instruction */ SPI_FLASH_SendByte(W25X_SectorErase); /* Send SectorAddr high nibble address byte */ SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16); /* Send SectorAddr medium nibble address byte */ SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8); /* Send SectorAddr low nibble address byte */ SPI_FLASH_SendByte(SectorAddr & 0xFF); /* Deselect the FLASH: Chip Select high */ SPI_FLASH_CS_HIGH(); /* Wait the end of Flash writing */ SPI_FLASH_WaitForWriteEnd();}
 
[/c]
W25x16块擦除参考时序图:

[c]
void SPI_FLASH_BulkErase(void){ /* Send write enable instruction */ SPI_FLASH_WriteEnable();
/* Bulk Erase */ /* Select the FLASH: Chip Select low */ SPI_FLASH_CS_LOW(); /* Send Bulk Erase instruction */ SPI_FLASH_SendByte(W25X_ChipErase); /* Deselect the FLASH: Chip Select high */ SPI_FLASH_CS_HIGH();
/* Wait the end of Flash writing */ SPI_FLASH_WaitForWriteEnd();}
 
[/c]
主函数如下:
[c]
/******************** (C) COPYRIGHT 2011 青风电子******************************** * 文件名 :main.c * 描述 :I2C 读写(AT24C02)测试。 * * 实验平台:QF-STM32F051开发板 * 库版本 :ST3.0.0**********************************************************************************//***头文件调用****/#include "stm32f0xx.h"#include "w25x16.h"#include "ili9328.h"
typedef enum { FAILED = 0, PASSED = !FAILED} TestStatus;__IO uint32_t DeviceID = 0;__IO uint32_t FlashID = 0;__IO TestStatus TransferStatus1 = FAILED;
/* 获取缓冲区的长度 */#define TxBufferSize1 (countof(TxBuffer1) - 1)#define RxBufferSize1 (countof(TxBuffer1) - 1)#define countof(a) (sizeof(a) / sizeof(*(a)))#define BufferSize (countof(Tx_Buffer)-1)
#define FLASH_WriteAddress 0x00000#define FLASH_ReadAddress FLASH_WriteAddress#define FLASH_SectorToErase FLASH_WriteAddress#define sFLASH_ID 0xEF3015
uint8_t Tx_Buffer[] = "good";uint8_t Rx_Buffer[];//-----------------------------------------------------------// * @brief Compares two buffers.// * @param pBuffer1, pBuffer2: buffers to be compared.// * @param BufferLength: buffer's length// * @retval PASSED: pBuffer1 identical to pBuffer2// * FAILED: pBuffer1 differs from pBuffer2// ----------------------------------------------------------------TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength){ while(BufferLength--) { if(*pBuffer1 != *pBuffer2) { return FAILED; }
pBuffer1++; pBuffer2++; }
return PASSED;}////////////////////////////////////////////////////void Delay(__IO uint32_t nCount){ for(; nCount != 0; nCount--);}///////////////////////////////////////////////////int main(void){ SystemInit(); SPI_FLASH_Init(); LCD_init(); // 液晶显示器初始化 LCD_Clear(ORANGE); // 全屏显示白色 POINT_COLOR =BLACK; // 定义笔的颜色为黑色 BACK_COLOR = WHITE ; // 定义笔的背景色为白色 DeviceID = SPI_FLASH_ReadDeviceID(); Delay( 200 ); /* Get SPI Flash ID */ FlashID = SPI_FLASH_ReadID(); LCD_ShowString(20,10,"FLASHID:"); LCD_ShowNum(84,10,FlashID,6);//读取FLASHID LCD_ShowString(20,30,"DeviceID:"); LCD_ShowNum(90,30,DeviceID,6);//读取DEVICEID SPI_FLASH_SectorErase(FLASH_SectorToErase); SPI_FLASH_BufferWrite(Tx_Buffer,0x00000, 5); LCD_ShowString(20,50, Tx_Buffer);//显示发送缓冲内的内容
SPI_FLASH_BufferRead(Rx_Buffer,0x00000, 5);//读取写入的内容 LCD_ShowString(20,70,"read:"); LCD_ShowString(60,70,Rx_Buffer);//显示接收缓冲内容
if(*Tx_Buffer==*Rx_Buffer) { LCD_ShowString(20,90,"w25q16 reading success"); } else { LCD_ShowString(20,90,"w25q16 reading error"); }//比较接收和发送的内容是否相同,相同则判断写入正确}
 
[/c]
实验现象:

液晶TFT显示我们目前对W25Q16的操作情况。
回复

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /2 下一条

手机版|小黑屋|与非网

GMT+8, 2024-4-20 18:27 , Processed in 0.105983 second(s), 16 queries , MemCache On.

ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

苏公网安备 32059002001037号

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.