stm32---SPI与外部FLASH

stm32外部flash是EN25QXX 芯片
使用 STM32F1 的 SPI 和外部 FLASH(EN25QXX)进行通信,实现数据的读写 操作。本章要实现的功能是:首先检测外部 FLASH 是否正常,然后使用 K__UP 和 K_DOWN 键控制 FLASH 的写入和读取,并将 数据显示在 串口助手上,同时控制 D1 指示灯不断闪烁,提示系统正常 运行


W25Q128 将 16M 的容量分为 256 个块( Block),每个块大小为 64K 字 节,每个块又分为 16 个扇区( Sector),每个扇区 4K 个字节。 W25Q128 的最小擦除单位为一个扇区,也就是每次必须擦除 4K 个字节。这样我们需要给 W25Q128 开辟一个至少 4K 的缓存区,这样对 SRAM 要求比较高,要求芯片必须 有 4K 以上 SRAM 才能很好的操作。


13367077-ff4aefb03d52b75d.PNG
W25Q128

13367077-147249c9efc203db.PNG
EN25QXX 芯片的操作

flash.c

#include "flash.h"
#include "spi.h"
#include "SysTick.h"   
#include "usart.h"

u16 EN25QXX_TYPE=EN25Q64;   //默认是EN25Q64

//4Kbytes为一个Sector
//16个扇区为1个Block
//EN25Q64
//容量为8M字节,共有128个Block(块),4096个Sector (扇区)
                                                     
//初始化SPI FLASH的IO口
void EN25QXX_Init()
{
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOG|RCC_APB2Periph_GPIOF, ENABLE);
    
    /* FLASH_CS PG13 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOG,&GPIO_InitStructure);
    GPIO_SetBits(GPIOG,GPIO_Pin_13);
    
    /*SD_CS置1  ,PG14*/
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
    GPIO_Init(GPIOG,&GPIO_InitStructure);
    GPIO_SetBits(GPIOG,GPIO_Pin_14);
    
    /*ENC28J60_CS置1  ,PB12*/
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOB,&GPIO_InitStructure);
    GPIO_SetBits(GPIOB,GPIO_Pin_12);
    
    /*NRF24L01_CS置1  ,PF9*/
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_Init(GPIOF,&GPIO_InitStructure);
    GPIO_SetBits(GPIOF,GPIO_Pin_9);
    
    EN25QXX_CS = 1; //EN25QXX的片选信号置1表示取消片选,这个是低电平有效
    SPI2_Init();
    EN25QXX_TYPE = EN25QXX_ReadID();
    
}

/*读取EN25QXX的状态寄存器
BIT7  6   5   4   3   2   1   0
SPR   RV  TB BP2 BP1 BP0 WEL BUSY
SPR:默认0,状态寄存器保护位,配合WP使用
TB,BP2,BP1,BP0:FLASH区域写保护设置
WEL:写使能锁定
BUSY:忙标记位(1,忙;0,空闲)
默认:0x00*/
u8 EN25QXX_ReadSR()
{
    u8 byte = 0;
    EN25QXX_CS = 0;     //使能器件
    SPI2_ReadWriteByte(EN25X_ReadStatusReg); //发送读取状态寄存器命令
    byte = SPI2_ReadWriteByte(0Xff); //读取一个字节
    EN25QXX_CS = 1; //取消片选
    return byte;
}

//写EN25QXX状态寄存器
//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
void EN25QXX_Write_SR(u8 sr)
{
    EN25QXX_CS = 0;  //使能器件
    SPI2_ReadWriteByte(EN25X_WriteStatusReg); //发送写取状态寄存器命令
    SPI2_ReadWriteByte(sr); //写入一个字节
    EN25QXX_CS = 1; //取消片选
}

//EN25QXX写使能    
//将WEL置位 
void EN25QXX_Write_Enable()
{
    EN25QXX_CS=0;                            //使能器件   
    SPI2_ReadWriteByte(EN25X_WriteEnable);      //发送写使能  
    EN25QXX_CS=1;                            //取消片选     
}

//EN25QXX写禁止    
//将WEL清零  
void EN25QXX_Write_Disable(void)   
{  
    EN25QXX_CS=0;                            //使能器件   
    SPI2_ReadWriteByte(EN25X_WriteDisable);     //发送写禁止指令    
    EN25QXX_CS=1;                            //取消片选               
} 

//读取芯片ID
//返回值如下:                   
//0XEF13,表示芯片型号为EN25Q80  
//0XEF14,表示芯片型号为EN25Q16    
//0XEF15,表示芯片型号为EN25Q32  
//0XEF16,表示芯片型号为EN25Q64 
//0XEF17,表示芯片型号为EN25Q128    
u16 EN25QXX_ReadID()
{
    u16 temp = 0;
    EN25QXX_CS = 0;
    SPI2_ReadWriteByte(0x90);
    SPI2_ReadWriteByte(0x00);
    SPI2_ReadWriteByte(0x00);
    SPI2_ReadWriteByte(0x00);
    temp |= SPI2_ReadWriteByte(0xff)<<8;
    temp |= SPI2_ReadWriteByte(0xff);
    EN25QXX_CS = 1;
    return  temp;
}


//读取SPI FLASH  
//在指定地址开始读取指定长度的数据
//pBuffer:读取的数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void EN25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)   
{ 
    u16 i;                                              
    EN25QXX_CS=0;                            //使能器件   
    SPI2_ReadWriteByte(EN25X_ReadData);         //发送读取命令   
    SPI2_ReadWriteByte((u8)((ReadAddr)>>16));  //发送24bit地址    
    SPI2_ReadWriteByte((u8)((ReadAddr)>>8));   
    SPI2_ReadWriteByte((u8)ReadAddr);   
    for(i=0;i<NumByteToRead;i++)
    { 
        pBuffer[i]=SPI2_ReadWriteByte(0XFF);   //循环读数  
    }
    EN25QXX_CS=1;                             
}  

//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:待输入的数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!   
void EN25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
    u16 i;  
    EN25QXX_Write_Enable();                  //SET WEL 
    EN25QXX_CS=0;                            //使能器件   
    SPI2_ReadWriteByte(EN25X_PageProgram);      //发送写页命令   
    SPI2_ReadWriteByte((u8)((WriteAddr)>>16)); //发送24bit地址    
    SPI2_ReadWriteByte((u8)((WriteAddr)>>8));   
    SPI2_ReadWriteByte((u8)WriteAddr);   
    for(i=0;i<NumByteToWrite;i++) 
    {
        SPI2_ReadWriteByte(pBuffer[i]);//循环写数  
    }
    EN25QXX_CS=1;                            //取消片选 
    EN25QXX_Wait_Busy();                       //等待写入结束
}

//无检验写SPI FLASH 
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能 
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void EN25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)   
{                    
    u16 pageremain;    
    pageremain=256-WriteAddr%256; //单页剩余的字节数                
    if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节
    while(1)
    {      
        EN25QXX_Write_Page(pBuffer,WriteAddr,pageremain);
        if(NumByteToWrite==pageremain)break;//写入结束了
        else //NumByteToWrite>pageremain
        {
            pBuffer+=pageremain;
            WriteAddr+=pageremain;  

            NumByteToWrite-=pageremain;           //减去已经写入了的字节数
            if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节
            else pageremain=NumByteToWrite;       //不够256个字节了
        }
    }       
} 

//写SPI FLASH  
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)                      
//NumByteToWrite:要写入的字节数(最大65535)   
u8 EN25QXX_BUFFER[4096];         
void EN25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)   
{ 
    u32 secpos;
    u16 secoff;
    u16 secremain;     
    u16 i;    
    u8 * EN25QXX_BUF;     
    EN25QXX_BUF=EN25QXX_BUFFER;      
    secpos=WriteAddr/4096;//扇区地址  
    secoff=WriteAddr%4096;//在扇区内的偏移
    secremain=4096-secoff;//扇区剩余空间大小   
    //printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用
    if(NumByteToWrite<=secremain)  secremain=NumByteToWrite;//不大于4096个字节
    while(1) 
    {   
        EN25QXX_Read(EN25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容
        for(i=0;i<secremain;i++)//校验数据
        {
            if(EN25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除       
        }
        if(i<secremain)//需要擦除
        {
            EN25QXX_Erase_Sector(secpos);//擦除这个扇区
            for(i=0;i<secremain;i++)       //复制
            {
                EN25QXX_BUF[i+secoff]=pBuffer[i];     
            }
            EN25QXX_Write_NoCheck(EN25QXX_BUF,secpos*4096,4096);//写入整个扇区  

        }else EN25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间.                 
        if(NumByteToWrite==secremain)break;//写入结束了
        else//写入未结束
        {
            secpos++;//扇区地址增1
            secoff=0;//偏移位置为0    

            pBuffer+=secremain;  //指针偏移
            WriteAddr+=secremain;//写地址偏移       
            NumByteToWrite-=secremain;              //字节数递减
            if(NumByteToWrite>4096)secremain=4096;  //下一个扇区还是写不完
            else secremain=NumByteToWrite;          //下一个扇区可以写完了
        }    
    }    
}

//擦除整个芯片          
//等待时间超长...
void EN25QXX_Erase_Chip(void)   
{                                   
    EN25QXX_Write_Enable();                  //SET WEL 
    EN25QXX_Wait_Busy();   
    EN25QXX_CS=0;                            //使能器件   
    SPI2_ReadWriteByte(EN25X_ChipErase);        //发送片擦除命令  
    EN25QXX_CS=1;                            //取消片选               
    EN25QXX_Wait_Busy();                   //等待芯片擦除结束
}   

//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个山区的最少时间:150ms
void EN25QXX_Erase_Sector(u32 Dst_Addr)   
{  
    //监视falsh擦除情况,测试用   
    printf("fe:%x\r\n",Dst_Addr);     
    Dst_Addr*=4096;
    EN25QXX_Write_Enable();                  //SET WEL   
    EN25QXX_Wait_Busy();   
    EN25QXX_CS=0;                            //使能器件   
    SPI2_ReadWriteByte(EN25X_SectorErase);      //发送扇区擦除指令 
    SPI2_ReadWriteByte((u8)((Dst_Addr)>>16));  //发送24bit地址    
    SPI2_ReadWriteByte((u8)((Dst_Addr)>>8));   
    SPI2_ReadWriteByte((u8)Dst_Addr);  
    EN25QXX_CS=1;                            //取消片选               
    EN25QXX_Wait_Busy();                   //等待擦除完成
}

//等待空闲
void EN25QXX_Wait_Busy(void)   
{   
    while((EN25QXX_ReadSR()&0x01)==0x01);   // 等待BUSY位清空
}

//进入掉电模式
void EN25QXX_PowerDown(void)   
{ 
    EN25QXX_CS=0;                            //使能器件   
    SPI2_ReadWriteByte(EN25X_PowerDown);        //发送掉电命令  
    EN25QXX_CS=1;                            //取消片选               
    delay_us(3);                               //等待TPD  
}   

//唤醒
void EN25QXX_WAKEUP(void)   
{  
    EN25QXX_CS=0;                            //使能器件   
    SPI2_ReadWriteByte(EN25X_ReleasePowerDown);   //  send EN25X_PowerDown command 0xAB    
    EN25QXX_CS=1;                            //取消片选               
    delay_us(3);                               //等待TRES1
} 

flash.h

#ifndef _flash_H
#define _flash_H

#include "system.h"


//EN25X系列/Q系列芯片列表      
//EN25Q80  ID  0XEF13
//EN25Q16  ID  0XEF14
//EN25Q32  ID  0XEF15
//EN25Q64  ID  0XEF16   
//EN25Q128 ID  0XEF17   
#define EN25Q80     0XEF13  
#define EN25Q16     0XEF14
#define EN25Q32     0XEF15
//#define EN25Q64   0XEF16
//#define EN25Q128  0XEF17
#define EN25Q64     0XC816
#define EN25Q128    0XC817

extern u16 EN25QXX_TYPE;                    //定义EN25QXX芯片型号        

#define EN25QXX_CS      PGout(13)       //EN25QXX的片选信号


//指令表
#define EN25X_WriteEnable       0x06  //写使能命令
#define EN25X_WriteDisable      0x04  //写禁止指令  
#define EN25X_ReadStatusReg     0x05 //读取状态寄存器命令
#define EN25X_WriteStatusReg    0x01 //写状态寄存器命令
#define EN25X_ReadData          0x03 //读取命令 
#define EN25X_FastReadData      0x0B 
#define EN25X_FastReadDual      0x3B 
#define EN25X_PageProgram       0x02 
#define EN25X_BlockErase        0xD8 
#define EN25X_SectorErase       0x20 
#define EN25X_ChipErase         0xC7 
#define EN25X_PowerDown         0xB9 
#define EN25X_ReleasePowerDown  0xAB 
#define EN25X_DeviceID          0xAB 
#define EN25X_ManufactDeviceID  0x90 
#define EN25X_JedecDeviceID     0x9F 

void EN25QXX_Init(void);
u16  EN25QXX_ReadID(void);                  //读取FLASH ID
u8   EN25QXX_ReadSR(void);              //读取状态寄存器 
void EN25QXX_Write_SR(u8 sr);           //写状态寄存器
void EN25QXX_Write_Enable(void);        //写使能 
void EN25QXX_Write_Disable(void);       //写保护
void EN25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);
void EN25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead);   //读取flash
void EN25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);//写入flash
void EN25QXX_Erase_Chip(void);          //整片擦除
void EN25QXX_Erase_Sector(u32 Dst_Addr);    //扇区擦除
void EN25QXX_Wait_Busy(void);               //等待空闲
void EN25QXX_PowerDown(void);           //进入掉电模式
void EN25QXX_WAKEUP(void);              //唤醒


#endif

main.c

#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "spi.h"
#include "flash.h"
#include "key.h"

const u8 text_buf[] = "www.baidu.com";
#define TEXT_LEN sizeof(text_buf)
int main()
{
    u8 i=0;
    u8 key;
    u8 buf[30];
    SysTick_Init(72);
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //中断优先级分组 分2组
    LED_Init();
    USART1_Init(9600);
    KEY_Init();
    EN25QXX_Init();
    
    while(EN25QXX_ReadID()!=EN25Q64)
    {
        printf("EN25Q64 Check Failed!\r\n");
    }
    printf("EN25Q64 Cheak Success!\r\n");
        
    while(1)
    {   
        key = KEY_Scan(0);
        if(key==KEY_UP)
        {
            EN25QXX_Write((u8 *)text_buf,0,TEXT_LEN);
            printf("发送的数据:%s\r\n",text_buf);
        }
        if(key==KEY_DOWN)
        {
            EN25QXX_Read(buf,0,TEXT_LEN);
            printf("接收的数据:%s\r\n",buf);
        }
        i++;
        if(i%20==0)
        {
            led1=!led1;
        }
        
        delay_ms(10);
            
    }
}
  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 野火是一家致力于开发和销售嵌入式开发板的公司,他们的产品系列中包括了很多基于STM32-F103芯片的开发板。这款芯片是意法半导体推出的一款低功耗、高性能的32位微控制器,广泛应用于工业控制、汽车电子、消费电子等领域。 野火的开发板采用了精美的设计和丰富的功能,使得学习和开发STM32-F103变得更加简单和有趣。它们不仅提供了各种接口和外设,如GPIO、串口、I2C、SPI等,还具备了丰富的软件资源,包括示例代码、库函数和开发工具等。这些资源可以帮助开发者快速上手,并且能够实现各种功能,如LED闪烁、蜂鸣器发声、温度传感器读取等。 野火开发板的另一个亮点是它们的零死角设计。无论是电源、传感器、扩展接口还是USB接口,野火都考虑到了用户的需求,并且尽可能地做到了完善和完整。这样一来,用户就可以在不受限制的情况下进行各种创意的嵌入式开发,不再为接口不够或功能不完善而烦恼。 与此同时,野火开发板也提供了丰富的学习资源和教学视频,帮助初学者了解和掌握STM32-F103的基本原理和应用。无论是学生、工程师还是爱好者,都可以通过野火的开发板来深入了解嵌入式系统的开发和应用,进一步提升自己的技能和能力。 总之,野火的开发板可以说是零死角玩转STM32-F103的理想选择。它们的丰富功能、完善的接口和外设,以及全面的学习资源,都使得开发者能够轻松地进行嵌入式系统的开发和应用。无论是初学者还是专业人士,都能够通过野火的产品来探索和实践嵌入式技术的魅力。 ### 回答2: 野火 零死角玩转STM32-F103是一本关于如何全面掌握野火STM32-F103开发板的书籍。 首先,野火STM32-F103是一款功能强大的开发板,搭载了STM32F103C8T6的微控制器芯片。该开发板具有丰富的外设资源,如LED指示灯、按键、蜂鸣器、ADC、串口等,可用于各种实际应用。在这本书中,通过详细的实例和案例,作者教读者如何充分利用这些外设实现各种功能。 此外,书中还详细介绍了STM32开发环境的搭建和使用。从软件安装到简单的“Hello World”程序的编写,再到更复杂的项目开发,都有详细的步骤和指导。读者可以根据书中的指导逐步学习和掌握STM32的开发工具和编程语言。 在书的后面章节中,还介绍了一些高级应用,如通过外部中断、定时器和PWM等方式控制外设,以及使用WiFi、蓝牙等无线通信模块进行远程控制等。这些内容可以帮助读者更加深入地理解STM32的应用场景,并扩展自己的知识和技能。 总之,野火 零死角玩转STM32-F103是一本全面而实用的书籍,适合初学者和有一定基础的读者。通过学习这本书,读者可以轻松地掌握STM32的开发技术,将其应用于自己的项目中。无论是做电子制作、嵌入式系统设计还是其他领域,这本书都会成为读者的得力助手。 ### 回答3: 野火是一款基于STM32-F103开发板的强大工具,它集成了丰富的硬件资源和强大的软件支持,让开发者能够充分发挥STM32-F103的潜力。野火开发板具有零死角的设计,使得开发者可以以各种方式玩转STM32-F103。 首先,野火开发板提供了丰富的外设接口,包括GPIO、UART、SPI、I2C、ADC等等,这些接口可以连接各种外部设备,如传感器、显示屏、通信模块等。同时,野火还配备了大容量存储器,包括FLASH和RAM,可以存储大量的代码和数据。 其次,野火开发板支持多种开发环境和编程语言,包括keil、IAR、STM32CubeMX等,可以满足不同开发者的需求。野火还提供了丰富的软件库和示例代码,开发者可以轻松地使用这些库和代码进行开发,加快开发进度。 最重要的是,野火具有强大的性能和稳定性,能够在各种应用场景下稳定运行。它采用了STM32-F103芯片,配备了高速处理器和丰富的内存,能够处理各种复杂的任务。此外,野火还支持多种调试功能,如JTAG、SWD和串口调试,方便开发者进行调试和测试。 总而言之,野火开发板零死角玩转STM32-F103,不仅提供了丰富的硬件资源和软件支持,还具有强大的性能和稳定性,能够满足开发者的各种需求。无论是初学者还是有经验的开发者,都可以通过野火开发板来开发出高质量的STM32-F103应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值