×
嵌入式 > 技术百科 > 详情

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

发布时间:2020-06-17 发布时间:
|

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("\n\r 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的操作情况。


『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
Keil5(MDK5)在调试(debug)过程中遇到的问题