STM32之SPI_FLASH(实例)

发布者:创意旅程最新更新时间:2018-09-19 来源: eefocus关键字:STM32  SPI  FLASH 手机看文章 扫描二维码
随时随地手机看文章

本实例用的是STM32F103VET6平台,它有3个SPI接口(这里使用SPI1),各信号线连接到FLASH(型号:W25X16)的CS,CLK,DO,DIO线,以实现SPI通讯,对FLASH进行读写. 
(这里采用主模式,全双工通讯,通过查询发送数据寄存器和接收数据寄存器状态确保通讯正常) 
这里写图片描述

mian函数: 
1#define sFLASH_ID 0xEF3015(前面加个1,免得变大) 
u32 DeviceID; 
u32 FlashID;

int main(void) 

/115200 8-N-1/ 
USART1_Config();

SPI_FLASH_Init();


DeviceID = SPI_FLASH_ReadDeviceID();

Delay(200);


FlashID = SPI_FLASH_ReadID();


printf("\r\n FlashID is 0x%X, Manufacturer Device ID is 0x%X\r\n",FlashID,DeviceID);


if(FlashID == sFLASH_ID)

{

    printf("\r\n 检测到 flash W25X16 !\r\n");


    SPI_FLASH_SectorErase(FLASH_SectorToErase);


    SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize);

    printf("\r\n 写入的数据为:%s \r\t", Tx_Buffer);


    SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);

    printf("\r\n 读出的数据为:%s \r\n", Tx_Buffer);


    TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);


    if( PASSED == TransferStatus1)

    {

        printf("\r\n 2M 串行 flash(W25X16)测试成功!\n\r");

    }

    else

    {

        printf("\r\n 2M 串行 flash(W25X16)测试失败!\n\r");

    }

}

else

{

    printf("\r\n 获取不到 W25X16 ID!\n\r");

}

SPI_Flash_PowerDown();

while(1);


mian函数的流程: 
1,调用 USART1_Config() 初始化串口; 
2,调用 SPI_FLASH_Init() 初始化SPI模块; 
3,调用 SPI_FLASH_ReadDeviceID 读取FLASH器件生产厂商的ID信息; 
4,调用 SPI_FLASH_ReadID 读取FLASH器件的设备ID信息; 
5,如果读取ID正确,则调用 SPI_FLASH_SectorErase()把FLASH内容擦除,擦除后调用 SPI_FLASH_BufferWrite()向FLASH写入数据,然后再调用 SPI_FLASH_BufferRead()从刚刚写入的地址中读出数据,最后调用 Buffercmp()对写入和读取的数据进行匹配,匹配成功则把标志变量 TransferStatus1赋值为 PASSED(自定义的枚举变量); 
6,根据标志量 TransferStatus1判断FLASH数据的:擦除,写入,读取是否正常,分情况输出到终端; 
7,如果读取FLASH的ID信息错误,则直接向终端输出检测不到FLASH信息; 
8,最后调用 SPI_Flash_PowerDown()函数关闭 FLASH设备的电源(因为数据写入到FLASH后并不会因断电而丢失,所以需要使用的时候再开启FLASH电源); 
PS: 
这里写图片描述 
这里写图片描述 
读取器件ID信息可以知道设备与主机是否能够正常工作,也便于区分不同的器件,可以在使用的FLASH用户数据手册找到ID表

SPI的初始化: 
void SPI_FLASH_Init(void) 

SPI_InitTypeDef SPI_InitStructure; 
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); 
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); 
/这里是GPIO初始化部分,将4个引脚都设定好/ 
/!< Configure SPI_FLASH_SPI pins: SCK / 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; 
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 
GPIO_Init(GPIOA, &GPIO_InitStructure); 
/!< Configure SPI_FLASH_SPI pins: MISO / 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; 
GPIO_Init(GPIOA, &GPIO_InitStructure); 
/!< Configure SPI_FLASH_SPI pins: MOSI / 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; 
GPIO_Init(GPIOA, &GPIO_InitStructure); 
/!< Configure SPI_FLASH_SPI_CS_PIN pin: SPI_FLASH Card CS pin / 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; 
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
GPIO_Init(GPIOA, &GPIO_InitStructure); 
/* Deselect the FLASH: Chip Select high */ 
SPI_FLASH_CS_HIGH(); //不用的时候就拉高 
/这里是SPI设置部分/ 
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; 
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; 
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; 
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; 
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; 
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; 
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; 
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; 
SPI_InitStructure.SPI_CRCPolynomial = 3; 
SPI_Init(SPI1, &SPI_InitStructure); 
/* Enable SPI1 */ 
SPI_Cmd(SPI1, ENABLE); 
}

GPIO初始化: 
根据《STM32数据手册》以及《STM32参考手册》,把PA5(SCK),PA6(MISO),PA7(MOSI)设置成复用推挽输出,因为PA4(NSS)是使用软件模式,所以设置为通用退完输出. 
这里写图片描述

SPI模式初始化: 
对于初始化,是需要根据通讯的设备FLASH的SPI特性来决定的,下面成员分析: 
SPI_InitStructure.SPI_Direction= SPI_Direction_2Lines_FullDuplex; 
这里设置通讯模式,这里设置成全双工模式(可以在keil环境下查找其他模式)

SPI_InitStructure.SPI_Mode = SPI_Mode_Master; 
这里是设置工作模式,STM32的SPI设备可以gon工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave),这两个模式最大的区别就是SPI的SCK信号线时序,SCK的时序是由通讯中的主机产生的,如果配置成从机模式,STM32的SPI模块将接收外来的SCK信号.(这里STM32作为SPI通讯主机,所以设置成 SPI_Mode_Master).

SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; 
这个是设置SPI每次通讯的数据大小(称为数据帧)为8位还是16位(从FLASH的数据手册可以查到,这里的FLASH的数据帧大小为8为,所以要把STM32的SPI模块设置相同的)

SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;&SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; 
这两个成员是配置SPI的时钟极性(CPOL)和时钟相位(CPHA),这两个配置影响到SPI的通讯模式,要设置成符合将要互相通讯的设备的要求. 
CPOL:可以取 SPI_CPOL_High(SPI 通讯空闲时 SCK 为高电平)或者SPI_CPOL_Low(SPI 通讯空闲时 SCK 为低电平); 
CPHA:可以取 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据)或者SPI_CPHA_2Edge (在 SCK 的偶数边沿采集数据);

这里写图片描述 
查询这个FLASH的使用手册,可以了解到这个FLASH支持以SPI的模式0和模式3通讯. 
模式0:在SPI空闲时,SCK为低电平,奇数边沿采样; 
模式3:在SPI空闲时,SCK为高电平,偶数变异采样; 
所以这里配置成模式3,把CPOL赋值为SPI_CPOL_High(SPI空闲时SCK为高电平),把CPHA赋值为SPI_CPHA_2Edge(在SCK的偶数边沿超级数据)

SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; 
这里是配置NSS引脚的使用模式,可以选择为硬件模式(SPI_NSS_Hard)与软件模式(SPI_NSS_Soft),在硬件模式中的SPI片选由硬件自动产生,而软件模式则需要手动把相应的FPIO端口拉高或拉低产生非片选和片选信号(如果外界条件允许,硬件模式还会自动将STM32的SPI设置为主机) 
这里是由软件产生模式,所以赋值为SPI_NSS_Soft.

SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; 
这里是设置波特率分频值,分频后的时钟为SPI的SCK信号线的时钟频率,这个成员可以设置为fpclk的2,4,6,8,32,64,128,256分频.这里设置为4分频

SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; 
所有串行的通讯协议都会有MSB先行(高位数据在前)还是LSB先行(地位数据在前)的问题,STM32的SPI模块可以通过对这个结构体成员,对这个特性编程控制. 
根据FLASH的通讯时序,这里设置为MSB先行(SPI_FirstBit_MSB)

SPI_InitStructure.SPI_CRCPolynomial = 3; 
这里是设置SPI的CEC校验的多项式,如果使用到CRC校验时,就是用这个成员的参数(多项式),来计算CRC的值.(这里的FLASH不支持CRC校验,所以赋值为3其实没意义)

配置完这些结构体成员后,调用 SPI_Init()把这些参数写入到寄存器中,然后调用SPI_Cmd()使能SPI1外设。 
PS: 
SPI_FLASH_CS_HIGH()这个实际是上一个自定义的宏: 
#define SPI_FLASH_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4) 
实际上这个宏就是用来把 PA4(NSS)引脚拉高,从而禁止SPI通讯 
#define SPI_FLASH_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4) 
如果要需要使用的时候,就直接拉低就行了这样就可以开始通讯了

控制FLASH的命令: 
因为不同的设备,都会相应的有不同的指令,如 EEPROM 中会把第一个数据解释为存储矩阵的地址(实质就是指令)。而 FLASH 则定义了更多的指令,有写指令,读指令,读 ID 指令等等。 
这些指令,对主机来说,只是它遵守最基本的通讯协议发送出的数据。但设备把这些数据解释成不同的意义(指令编码),所以才成为指令。在我们配置好 STM32 的协议模块后,想要控制设备,就要遵守相应设备所定义的命令规则。 
这里写图片描述 
这里写图片描述

指 令 表 中 的 A0~A23 指 地 址 ; M0~M7 为 器 件 的 制 造 商 ID(MANUFACTURER ID);D0~D7 为数据。

读取FLASH ID: 
在命令列表可以了解到读取设备 ID 的命令(Device ID)编码为 ABh、dummy、dummy、dummy。表示此命令由这四个字节组成,其中dummy意为任意编码,即这几个字节必须发送数据,但这些数据是任意的,命令列表中带括号的字节数据表示由FLASH返回给主机的响应,可以看到Device ID命令的第5个字节为从机返回的响应,(ID7~ID0),即返回设备的ID号.

这里写图片描述 
使用DeviceID命令时的时序图 
可以看到主机首先通过MOSI线(即FLASH的DIO线)发送第一个字节为ABh编码,紧接着三个字节的dummy编码,然后FLASH就忽略DIO线上的信号,通过MISO线(即FLASH的DO线)把它的FLASH设备ID发送给主机.

u32 SPI_FLASH_ReadDeviceID(void) 

u32 Temp = 0; 
/使用的时候就拉低/ 
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; 

SPI_FLASH_CS_LOW(); 
把片选拉低,开始通讯

SPI_FLASH_SendByte(W25X_DeviceID); 
向FLASH发送一个命令字节编码:W25X_DeviceID (这里定义的宏为:0XAB)

SPI_FLASH_SendByte(Dummy_Byte); 
根据指令表,发送完指令后,后面要接着发送三个字节的dummy_Byte(这里宏定义为:0xff,设置为其他也无所谓)

Temp = SPI_FLASH_SendByte(Dummy_Byte); 
在前面发送完三个字节的 Dummy_Byte后,在第五个字节,FLASH通过DIO端口输出它的器件ID,所以这里再调用一次SPI_FLASH_SendByte(Dummy_Byte)接收返回值,赋值给Temp.

SPI_FLASH_CS_HIGH(); 
把片选拉高,结束通讯

这样就完成了读取FLASH ID,这里有一个相对底层的函数SPI_FLASH_SendByte(),它实现了利用SPI发送和接收数据的功能 
u8 SPI_FLASH_SendByte(u8 byte) 

/等待发送数据寄存器清空/ 
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); 
/发送数据/ 
SPI_I2S_SendData(SPI1, byte); 
/等待接收数据寄存器为非空/ 
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); 
/返回接收到的数值/ 
return SPI_I2S_ReceiveData(SPI1); 

流程: 
1,调用库函数 SPI_I2S_GetFlagStatus()等待发送数据寄存器清空; 
2,发送数据寄存器准备好后,调用库函数SPI_I2S_SendData()向从机发送数据; 
3,调用库函数SPI_I2S_GetFlagStatus()等待接收数据寄存器非空; 
4,接收寄存器非空时,调用SPI_I2S_ReceiveData()获取接收寄存器中的数据并作为函数的返回值,这个数据即由从机发送给主机的数据; 
这是最底层的发送数据和接收数据的函数,利用了库函数的标志检测确保通讯正常。

读取厂商ID: 
u8 SPI_FLASH_ReadID(void) 

u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;

SPI_FLASH_CS_LOW();//拉低开始通讯


SPI_FLASH_SendByte(W25x_JedecDeviceID);


Temp0 = SPI_FLASH_SendByte(Dummy_Byte);


Temp1 = SPI_FLASH_SendByte(Dummy_Byte);


Temp2 = SPI_FLASH_SendByte(Dummy_Byte);


Temp = (Temp0 << 16) | (Temp1 << 8) | (Temp2);


SPI_FLASH_CS_HIGH();


return Temp;  


这个函数和之前的读取设备ID流程也是类型的,差别在于发送一个字节的命令编码JEDEC ID(9Fh)之后,从机就通过D0线返回厂商ID以及0~16位的设备ID。

这里写图片描述 
读厂商ID时序图

擦除FLASH内容: 
扇区擦除(根据FLASH的储存原理,在写入数据前,要先对存储区域进行擦除,也叫预写)

void SPI_FLASH_SectorErase(u32 SectorAddr) 

/写使能并且判断FLASH状态/ 
SPI_FLASH_WriteEnable(); 
SPI_FLASH_WaitForWriteEnd();


/*这里开始是FLASH擦除操作*/

SPI_FLASH_CS_LOW();


SPI_FLASH_SendByte(W25X_SectorErase);

/*这里是擦除一个扇区,也就是4KB*/

SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);


SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);


SPI_FLASH_SendByte(SectorAddr & 0xFF);


SPI_FLASH_CS_HIGH();

/*再次判断FLASH状态确保可以执行下一次操作*/

SPI_FLASH_WaitForWriteEnd();    


这里写图片描述 
这是扇区擦除时序,其中的第一个字节为扇区擦除命令编码(20h),紧跟其后的为要进行擦除的,根据FLASH的说明,整个存储矩阵分为块区和扇区,每块(Block)的大小为64KB,每个扇区(Sector)的大小为4KB,对存储矩阵进行擦除时,最小的单位为扇区.

写使能: 
void SPI_FLASH_WriteEnable(void) 

SPI_FLASH_CS_LOW();


SPI_FLASH_SendByte(W25X_WriteEnable);


SPI_FLASH_CS_HIGH();


这里写图片描述 
这里根据写使能命令时序,只要发送命令WriteEnable(06h)就行了。

读FLASH状态: 
在擦除操作之前,需要调用SPI_FLASH_WaitForWriteEnd()来确保FLASH不忙碌的时候,才发送命令或者数据,通过读取FLASH的状态寄存器来获知他的工作状态.

void SPI_FLASH_WaitForWriteEnd(void) 

u8 FLASH_Status = 0;


SPI_FLASH_CS_LOW();


SPI_FLASH_SendByte(W25X_ReadStatusReg);

/*一直检测FLASH状态寄存器状态,直到Bit0位(BUSY位)为0)*/

do

{

    FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);

}while((FLASH_Status & WIP_Flag) == SET);


SPI_FLASH_CS_HIGH();


这里写图片描述 
整个函数实质是不断的循环检测FLASH状态寄存器的Busy位,知道FLASH的内部写时序完成,从而确保下一通讯操作正常.主机通过发送读状态寄存器命令Read Status Register(05h 编码),返回的为他的8为状态寄存的值.

这里写图片描述 
检测FLASH的状态寄存器的Bit0(BUSY位),当FLASH在执行内部写时序的时候,除了读状态寄存器命令,其他的一切命令他都会忽略,并且BUSY位保持为1,所以我们需要等待BUSY位为0的时候,再向FLASH发送其他命令.

向FLASH写入数据: 
对FLASH写入数据,最小单位是256字节,厂商把这个单位曾为页.写入时,一般也只有页写入的方式,所以为了方便的把一个很长的数据写入到FLASH时,一般需要进行转换,把数据按页分好,再写入到FLASH中(类似于I2C对EEPROM的页写入,只是页的大小不同而已).

void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite) 

u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp= 0; 
/这里划分好数据需要写多少页,写地址,写大小/ 
Addr = WriteAddr % SPI_FLASH_PageSize; 
count = SPI_FLASH_PageSize - Addr; 
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize; 
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;


if (Addr == 0)

{

    if(NumOfPage == 0)

    {

        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);

    }

    else

    {

        while(NumOfPage--)

        {

            SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);


            WriteAddr += SPI_FLASH_PageSize;

            pBuffer += SPI_FLASH_PageSize;

        }

        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);

    }

}

else

{

    if(NumOfPage == 0)

    {

        if(NumOfSingle > count)

        {

            temp = NumOfSingle - count;

            SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);

            WriteAddr += count;

            pBuffer += count;

            SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);

        }

        else

        {

            SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite)

        }

    }

    else

    {

        NumByteToWrite -= count;

        NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;

        NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

        SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);

        WriteAddr += count;

        pBuffer += count;

        while (NumOfPage--)

        {

        SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageS

        WriteAddr += SPI_FLASH_PageSize;

        pBuffer += SPI_FLASH_PageSize;

        }


        if(NumOfSingle != 0)

        {

            SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);

        }

    }

}


对数组进行分页后,就调用SPI_FLASH_PageWrite()对数据进行按页写入(是不是和I2C写入EEPROM的写函数一样(连行数都差不多-_-!),不了解的话可以去看之前的I2C部分)

底层写操作:SPI_FLASH_PageWrite() 
void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite) 

/写使能/ 
SPI_FLASH_WriteEnable(); 
/拉低开始通讯/ 
SPI_FLASH_CS_LOW(); 
/发送PageProgram(02h))/ 
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); 
/这里是判断写大小是否符合FLASH规定的256/ 
if(NumByteToWrite > SPI_FLASH_PerWritePageSize) 

NumByteToWrite = SPI_FLASH_PerWritePageSize; 
//printf(“\n\r Err: SPI_FLASH_PageWrite too large!”); 

/这里才是写真是数据/ 
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(); 
}

这里写图片描述 
发送完写入命令Page Program(编码 02h)及地址之后,可以连续写入最多256个字节的数据(SPI_FLASH_PerWritePageSize = 256),在发送完数据之后,记得调用SPI_FLASH_WaitForWriteEnd()等待FLASH内部写时序完成再推出函数.

从FLASH读取数据: 
对于读取数据,发送一个命令后,可以无限制的一直把整个FLASH的数据都读取完,直到读取的数据量足够了,就拉高片选信号以表示读取数据结束.

void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead) 

SPI_FLASH_CS_LOW(); 
SPI_FLASH_SendByte(W25X_ReadData); 
/* Send ReadAddr high nibble address byte to read from */ 
SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16); 
/* Send ReadAddr medium nibble address byte to read from */ 
SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8); 
/* Send ReadAddr low nibble address byte to read from */ 
SPI_FLASH_SendByte(ReadAddr & 0xFF); 
while (NumByteToRead–) 

/* Read a byte from the FLASH */ 
*pBuffer = SPI_FLASH_SendByte(Dummy_Byte); 
/* Point to the next location where the byte read will be saved*/ 
pBuffer++; 

/* Deselect the FLASH: Chip Select high */ 
SPI_FLASH_CS_HIGH(); 
}

这里写图片描述 
首先发送一个读取数据命令 Read Data(03h),接着发送24位读数据起始地址,STM32再通过D0线接收数据,并使用指针的方式记录起来.

PS: 
通过以上的函数,就可以实现对FLASH的擦除,写入和读取操作.STM32与SPI-FLASH通讯步骤: 
1,配置I/O端口,使能GPIO; 
2,根据将要进行通讯器件的SPI模式配置STM32的SPI,使能SPI时钟 
3,配置好SPI后,根据各种FLASH定义的命令控制,进行读写操作(注意:在写操作之前要先进行存储扇区的擦除操作,擦除操作前要先发出写使能命令!!)


关键字:STM32  SPI  FLASH 引用地址:STM32之SPI_FLASH(实例)

上一篇:关于STM32单片机移植FATFS
下一篇:STM32CubeMX学习教程之一:GPIO输出之跑马灯

推荐阅读最新更新时间:2024-03-16 16:14

STM32—SysTick使用方法
一、STM32的SysTick简介   SysTick是一个24位的系统节拍定时器system tick timer,SysTick,具有自动重载和溢出中断功能,所有基于Cortex_M3处理器的微控制器都可以由这个定时器获得一定的时间间隔。 systick的作用:    在单任务引用程序中,因为其架构就决定了它执行任务的串行性,这就引出一个问题:当某个任务出现问题时,就会牵连到后续的任务,进而导致整个系统崩溃。要解决这个问题,可以使用实时操作系统(RTOS).   因为RTOS以并行的架构处理任务,单一任务的崩溃并不会牵连到整个系统。这样用户出于可靠性的考虑可能就会基于RTOS来设计自己的应用程序。这样SYSTICK
[单片机]
<font color='red'>STM32</font>—SysTick使用方法
HOLTEK推出BS82D20A-3 Flash触控单片机
继BS82C16A-3之后,Holtek再度推出新一代内建LED/LCD Driver的Flash触控MCU BS82D20A-3,支持高达20个触控按键,除了保有上一代的优点之外还比上一代触控MCU更省电、触控侦测的更新率更高,并且抗干扰的能力更好,而内建的LED/LCD Driver更俱备4段电流输出控制,可直推LED/LCD不须外挂限流电阻或三极管,大幅简化产品应用零件及降低成本,非常适合于俱备LED/LCD的小家电触控面板应用,BS82D20A-3同时也支持I²C/UART界面,可当主控IC亦可搭配主控MCU专门负责触控按键的侦测或LED/LCD的显示。 BS82D20A-3的特点在于工规 (-40℃ ~ 8
[单片机]
HOLTEK推出BS82D20A-3 <font color='red'>Flash</font>触控单片机
关于STM32的几点重要内容
主要内容: 1.为什么STM32F0没有AFIO时钟呢? 2.关于STM32中的各种电源 3.IAR中如何查看外设寄存器 4.关于问问题 1为什么STM32F0没有AFIO时钟呢? 前面写过一篇文章【 关于STM32时钟配置的那些坑 】里面有讲述关于什么时候开启AFIO时钟,有朋友下来去研究AFIO时钟时发现STM32F0芯片中没有AFIO时钟,于是就问了这么一个问题:你好,为什么STM32F0没有AFIO时钟呢? 答案就是STM32F0没有AFIO时钟。那又是怎样实现重定义这个功能的呢? 实现重定义功能也是由特定的控制器完成的,需要使用某个控制器,就需要开启对应的时钟。 而STM32F1就是由“Alterna
[单片机]
关于<font color='red'>STM32</font>的几点重要内容
STM32复用的GPIO引脚配置方式
STM32所有内置外设的外部引脚都是与标准GPIO引脚复用的,例如对于STM32F103VBT6,47引脚为PB10,它的复用功能是 I2C2_SCL和USART3_TX,表示在上电之后它的默认功能为PB10,而I2C2的SCL和USART3的TX为它的复用功能;另外在TIM2 的引脚重映射后,TIM2_CH3也成为这个引脚的复用功能。 STM32基本上每个引脚都有8种配置模式: 1)浮空输入 2)带弱上拉输入 3)带弱下拉输入 4)模拟输入 5)推挽输出 6)开漏输出 7)复用推挽输出 8)复用开漏输出 通常有5种方式使用某个引脚功能,它们的配置方式如下: 1)作为普通GPIO输入:根据需要配置该引脚为浮空输入、带弱上拉输
[单片机]
STM32基础知识4-va_list原理及用法
VA_LIST 是在C语言中解决变参问题的一组宏,变参问题是指参数的个数不定,可以是传入一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有实际的名称与之相对应,用起来是很灵活。 下面是va_list的用法示例 : #include stdarg.h int AveInt(int,...); void main() { printf( %d/t ,AveInt(2,2,3)); printf( %d/t ,AveInt(4,2,4,6,8)); return; } int AveInt(int v,...) { int ReturnValue=0;
[单片机]
STM32中为什么要引入链表?
1、为何引入链表 在程序中经常面临一个问题,我们需要保存一定数量的对象,但是对象数目是不确定的,或者说是随时增加或减少的。这时候最简单的方法是创建一个足够大的数组,用来存储这些对象。我最近开发一个项目就遇到类似的问题,下面我把问题简化一下。 需求:通过PC下发一些矩形的坐标和宽高信息,每个区域有个ID编号,并在这些矩形内填充一定的数据。 通常情况下,最简单易懂的做法是,限制最多5个区域,每个区域存储1K数据。因此设置了这样的一个结构体(类似于面向对象语言里说的成员属性)。 typedef struct Area_Inf{ uint8_t ID; uint8_t X; uint8_t Y; uint8_t Width;
[单片机]
在<font color='red'>STM32</font>中为什么要引入链表?
单片机ROM,RAM和FLASH的作用
常规上ROM是用来存储固化程序的,RAM是用来存放数据的。由于FLASH ROM比普通的ROM读写速度快,擦写方便,一般用来存储用户程序和需要永久保存的数据。譬如说,现在家用的电子式电度表,它的内核是一款单片机,该单片机的程序就是存放在ROM里的。电度表在工作过程中,是要运算数据的,要采集电压和电流,并根据电压和电流计算出电度来。电压和电流时一个适时的数据,用户不关心,它只是用来计算电度用,计算完后该次采集的数据就用完了,然后再采集下一次,因此这些值就没必要永久存储,就把它放在RAM里边。然而计算完的电度,是需要永久保存的,单片机会定时或者在停电的瞬间将电度数存入到FLASH里。 --ROM存放指令代码和一些固定数值,程序运行后
[单片机]
初学stm32-寄存器开发点灯、流水灯、蜂鸣器
寄存器开发概述1 寄存器: 寄存器的功能是存储二进制代码,它是由具有存储功能的触发器组合起来构成的。一个触发器可以存储1位二进制代码,故存放n位二进制代码的寄存器,需用n个触发器来构成。 在计算机领域,寄存器是CPU内部的元件,包括通用寄存器、专用寄存器和控制寄存器。寄存器拥有非常高的读写速度,所以在寄存器之间的数据传送非常快。 STM32内部的所有寄存器都有唯一的地址 寄存器地址 = 寄存器基地址+偏移地址(偏移量) stm32时钟源: LSI RC 32KHZ — 低速内部振荡时钟源 LSE OSC 32.768KHZ — 低速外部晶振时钟源 16MHZ HSI RC — 高速内部振荡时钟源 4-26MHZ HSE
[单片机]
初学stm32-寄存器开发点灯、流水灯、蜂鸣器
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved