柒:SPI总线,实现外部FLASH(W25Q128)读写

目录:
SPI简述
SPI总线网络
SPI工作机制
SPI初始化步骤
code


SPI简述:

1:SPI是一种高速,全双工的,同步的通信总线;
2:四线连接,MISO-主设备数据输入,从设备数据输出;MOSI-主设备数据输出,从设备数据输入;SCLK-时钟信号,由主设备产生;CS-从设备片选信号,由主设备控制。
3:主机和从机各有一个串行移位寄存器,两个寄存器中的数据在同一时刻被交换,如果只进行写操作,主机只需要忽略接收到的字节;如果主机要读取从机中的字节,就必须发送一个空字节来引发从机的传输。
4:SPI可以设置输出串行时钟的极性和相位,时钟极性与串行同步时钟的空闲状态的电平有关,CPOL=0,串行同步时钟空闲状态为低电平,反之为高;CPHA可以设置选择两种不同的传输协议,CPHA=0,串行同步时钟的第一个跳边沿数据被采样,反之,在串行同步时钟的第二个跳边沿数据被采样;
5:为了防止MISO总线冲突,同一时间只允许一个从设备与主设备通讯;
6:SPI主机和从机的时钟极性和相位应该一致;
7:在数据传输(数据交换)的过程中,每次接收到的数据必须在下一次数据传输之前被采样.如果之前接收到的数据没有被读取,那么这些已经接收完成的数据将有可能会被丢弃,导致SPI物理模块最终失效。因此,在程序中一般都会在SPI传输完数据后,去读取SPI设备里的数据,即使这些数据(DummyData)在我们的程序里是无用的。


SPI总线网络
柒:SPI总线,实现外部FLASH(W25Q128)读写_第1张图片

SPI工作机制:

1:概述
2:时序

SPI初始化步骤:

1:配置相关引脚复用为SPI,使能SPI时钟;
2:设置SPI工作模式,包括主机或者从机、数据格式(高位在前还是低位在前)、设置串行时钟的极性和相位(采样方式)、SPI时钟频率(SPI的传输速度);
3:使能SPI;


code

spi.c-SPI驱动

/************************************************
*BOARD:STM32F103ZET6
*AUTHOR:Golf
*************************************************/
#include "spi.h"

void spi_init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;                       //定义gpio结构体  
    SPI_InitTypeDef SPI_InitStruct;                         //SPI结构体初始化
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);    //使能GPIOB口时钟
    RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2,  ENABLE ); //SPI2时钟使能  
    
    //GPIO结构体初始化
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;            //GPIO复用推挽
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;  //SPI2_NSS复用 启用软件从设备管理 NSS引脚上的电平由SI位确定
    GPIO_Init(GPIOA,&GPIO_InitStruct);     

    //SPI结构体初始化
    SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; 
    SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;
    SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;
    SPI_InitStruct.SPI_CRCPolynomial = 7;
    SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; 
    SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
    SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;                             //启动软件从设备管理
    SPI_Init(SPI2,&SPI_InitStruct); 
    
    SPI_Cmd(SPI2,ENABLE);                                               //spi2使能
}

//SPI发送一个字节 
void SPI_Send_Byte(uint16_t data)
{
    uint8_t cnt = 0;
    while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE) == RESET)
    {
        if(++cnt > 200)
            return;
    }
    SPI_I2S_SendData(SPI2,data);
}

//SPI接受一个字节
unsigned char SPI_Receive_Byte(void)
{
    uint8_t cnt = 0;
    while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE) == RESET)
    {
        if(++cnt > 200)
            return 0;
    }
    
    return SPI_I2S_ReceiveData(SPI2);
}

flash.c-w25q128驱动

#include "w25q128.h"

//指令定义
#define Write_Enable 0x06                  //写使能
#define Write_Enable_Volatile_SR 0x50      //易失性寄存器写使能
#define Write_Disable 0x04                 //写禁止
#define Read_SR1 0x05                      //读状态寄存器1
#define Read_SR2 0x35                      //读状态寄存器2  
#define Write_Status_Register 0x01         //写状态寄存器
#define Read_Data 0x03                     //读数据                   
#define Page_Program 0x02                  //页编程命令 
#define Sector_Erase 0x20                  //扇区擦除 
#define Block_Erase_32K 0x52               //32K块擦除
#define Block_Erase_64K 0xd8               //64K块擦除 
#define Chip_Erase 0xc7                    //芯片擦除 0x60
#define Power_Down 0xB9                    //Power_Down模式
#define Release_Power_Down 0xab            //恢复power_down模式
#define Read_Chip_ID 0x90                  //读取设备ID

#define CS  PBout(12)

//初始化SPI FLASH的IO口
void W25Q128_Init(void)
{ 
  GPIO_InitTypeDef  GPIO_InitStructure;
 
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
  GPIO_SetBits(GPIOB,GPIO_Pin_12);
    
  CS=1;                                                   //SPI FLASH不选中
  SPI1_Init();                                              //初始化SPI
}


//等待空闲 超时退出
void Wait_idle(void) 
{
    uint8_t cnt = 0;
    while((Read_SR1()&0x01) != 0x00)
    {
        if(cnt++ > 200)   //200超时,退出循环
            break;
    }
}

//写使能  06H
void Write_Enable(void)
{
    CS = 0;                              //拉低时钟
    Spi_Send_Receive_Data(Write_Enable); //发送06H
    CS = 1;                              //拉高时钟
}

//易失性寄存器写使能  50H
void Write_Enable_Volatile_SR(void)
{
    CS = 0;                                          //拉低时钟
    Spi_Send_Receive_Data(Write_Enable_Volatile_SR); //发送50H
    CS = 1;                                          //拉高时钟
}

//写禁止
void Write_Disable(void)
{
    CS = 0;                               //拉低时钟
    Spi_Send_Receive_Data(Write_Disable); //发送50H
    CS = 1;                               //拉高时钟
}

/*********************************************
*FUNC:读状态寄存器1
*INSTRUCTION:发送读状态寄存器1指令05H,返回状态寄存器1的值,返回的字节数,取决于主机发送的字节数,此指令可以在任何时候使用
**********************************************/
uint8_t Read_SR1(void)
{
    uint8_t dat = 0;
    CS = 0;
    Spi_Send_Receive_Data(Read_SR1);    //发送05H  
    dat = Spi_Send_Receive_Data(0xff);  //发送一个字节0xff,返回一个字节(寄存器的值)
    CS = 1;
    return dat;
}

/*********************************************
*FUNC:读状态寄存器2
*INSTRUCTION:发送读状态寄存器1指令35H,返回状态寄存器1的值,返回的字节数,取决于主机发送的字节数,此指令可以在任何时候使用
**********************************************/
uint8_t Read_SR2(void)
{
    uint8_t dat = 0;
    CS = 0;
    Spi_Send_Receive_Data(Read_SR2);    //发送35H  
    dat = Spi_Send_Receive_Data(0xff);  //发送一个字节0xff,返回一个字节(寄存器的值)
    CS = 1;
    return dat;
}

/*********************************************
*FUNC:写非易失性状态寄存器
*INSTRUCTION:写状态寄存器中非易失性的位,只对SRP0/SEC/TB/BP/CMP/LB/QE/SRP1有效
*********************************************/
void Write_SR_No_Volatile(uint16_t dat)
{
    Write_Enable();                                  //写使能 06H
    CS = 0;
    Spi_Send_Receive_Data(Write_Status_Register);    //发送01H  
    Spi_Send_Receive_Data((uint8_t)(dat>>8));        //发送dat数据  
    Spi_Send_Receive_Data((uint8_t)dat); 
    CS = 1;
    Wait_idle();                                     //等待空闲
}

/*********************************************
*FUNC:写易失性状态寄存器
*INSTRUCTION:05H+01H
*********************************************/
void Write_SR_Volatile(uint16_t dat)
{
    CS = 0;
    Write_Enable_Volatile_SR();                      //写使能 50H
    Spi_Send_Receive_Data(Write_Status_Register);    //发送01H  
    Spi_Send_Receive_Data((uint8_t)(dat>>8));        //发送dat数据  
    Spi_Send_Receive_Data((uint8_t)dat); 
    CS = 1;
    Wait_idle();                                     //等待空闲
}

/*********************************************
*FUNC:从存储器读一个或者多个字节
*INSTRUCTION:可以从起始地址,读取单个或多个字节的数据,最多可以一次读出一个芯片 
*********************************************/
void Read_Data(uint8_t *ptr,uint32_t address,uint8_t num)
{
    CS = 0;
    Spi_Send_Receive_Data(Read_Data);                 //0x03指令 
    Spi_Send_Receive_Data((uint8_t)(address >> 16));  //发送存储器地址 24位
    Spi_Send_Receive_Data((uint8_t)(address >> 8));
    Spi_Send_Receive_Data((uint8_t)address);
    for(;num>0;num--)
    {
        *(ptr+i) = Spi_Send_Receive_Data(0xff);
    }
    CS = 1;
}

/***********************************************
*FUNC:页编程 每页256字节
*INSTRUCTION:也编程之前 应该擦除flash 允许1-256字节写入
***********************************************/ 
void Page_Program(uint8_t *ptr,uint32_t address,uint32_t num)
{
    Write_Enable();                                   //写使能 06H
    CS = 0;
    Spi_Send_Receive_Data(Page_Program);              //发送0x02指令
    Spi_Send_Receive_Data((uint8_t)(address >> 16));  //发送存储器地址 24位
    Spi_Send_Receive_Data((uint8_t)(address >> 8));
    Spi_Send_Receive_Data((uint8_t)address);
    for(;num>0;num--)
    {
        Spi_Send_Receive_Data(*(ptr+i));
    }
    CS = 1;
    Wait_idle();                              //等待空闲 
}

//扇区内操作函数 
void Handle_Sector(uint8_t *ptr,uint32_t address,uint32_t Byte_Num)
{
    pageremain = 256 - address%256;           //判断address所在页的剩余字节 
    if(Byte_Num < 256)
        pageremain = Byte_Num;
    else
        pageremain = 256;
    while(1)                                  //使用循环进行页编程
    {
        Page_Program(ptr,address,pageremain);
        if(pageremain == Byte_Num)
            break;
        address += pageremain;
        ptr += pageremain;
        Byte_Num -= pageremain;
        if(Byte_Num > 256) 
            pageremain = 256;
        else
            pageremain = Byte_Num;
    }
}

/***********************************************
*FUNC:写Flash
*INSTRUCTION:
*1:最终调用页编程指令;
*2: 判断扇区是否已擦除;
*3: 擦除的最小单位是扇区;
*4: 首先对地址进行分扇区,擦除当前扇区,再写扇区,然后进入下一个扇区进行写操作,每进入一个扇区都要判断当前所需写字节数量是否超出扇区范围
*5:扇区操作里,调用页编程函数,判断是否写结束。
*6: 此代码存储数据总是从扇区的第一个地址开始存储
***********************************************/
uint8_t Flash[4096];
void Write_Flash(uint8_t *ptr,uint32_t address,uint32_t Byte_Num)
{
    uint8 i;
    uint8_t *p;
    uint16_t secpos = 0;           //起始地址所在的扇区位置
    uint16_t secoff = 0;           //起始地址在当前扇区的偏移值
    uint16_t secremain = 0;        //起始地址开始,当前扇区剩余字节数  
    uint8_t pageremain = 0;
    
    secpos = address/4096;
    secoff = address%4096;
    secremain = 4096 - secoff; 
    p = Flash;
    if(Byte_Num < 4096)            //当前扇区剩余字节够存放Byte_Num字节的数据
        secremain = Byte_Num;
    else
        secremain = 4096;
    while(1)
    {
        Sector_Erase(secpos);        //擦数当前扇区,判断是都擦除成功 
        Read_Data(p,secpos*4096,4096);
        for(i=0;i<4096;i++)
        {
            if(p[i] != 0xff)
                break;                   //擦除失败
        }
        Handle_Sector(ptr,secpos*4096,secremain);
        if(secremain == Byte_Num)    //写FLASH结束
            break;
        else
        {
            secpos++;
            ptr += secremain;
            Byte_Num -= secremain;
            if(Byte_Num < 4096)
                secremain = Byte_Num;
            else
                secremain = 4096;
        }
    }
}
/***********************************************
*FUNC:擦除一个扇区
*INSTRUCTION:执行此命令之前,必须进行写实能命令,擦除4K-BYTE的空间,擦完之后,全部为0xff
***********************************************/
void Sector_Erase(uint32_t address)
{
    Write_Enable();                                             //写使能 06H
    CS = 0;
    Spi_Send_Receive_Data(Page_Program);                        //发送0x20指令
    Spi_Send_Receive_Data((uint8_t)(address >> 16));            //发送存储器地址 24位
    Spi_Send_Receive_Data((uint8_t)(address >> 8));
    Spi_Send_Receive_Data((uint8_t)address);
    CS = 1;
    Wait_idle();                                                //等待空闲 
}

/**********************************************
*FUNC:擦除一个block
*INSTRUCTION:参数可写32k-byte和64k-byte,擦除完后全为0xff,首先进行写使能指令?
**********************************************/
void Block_Erase(uint8_t cmd,uint32_t address)
{
    Write_Enable();                                             //写使能 06H
    CS = 0;
    Spi_Send_Receive_Data(cmd);      
    Spi_Send_Receive_Data((uint8_t)(address >> 16));            //发送存储器地址 24位
    Spi_Send_Receive_Data((uint8_t)(address >> 8));
    Spi_Send_Receive_Data(uint8_t)address);
    CS = 1;
    Wait_idle();                                                //等待空闲 
}

/**********************************************
*FUNC:擦除整个芯片
*INSTRUCTION:发送0xc7/0x60就可擦除整个芯片 擦完后全为0xff
**********************************************/
void Chip_Erase(void)
{
    Write_Enable();                                             //写使能 06H
    CS = 0;
    Spi_Send_Receive_Data(Chip_Erase);
    CS = 1;
    Wait_idle();                                                //等待空闲 
}

/**********************************************
*FUNC:模块进入掉电模式,使得待机电流最小
*INSTRUCTION:进入此模式后,只有Release from Power-Down指令才能使芯片恢复到正常状态
**********************************************/
void Power_Down(void)
{
    CS = 0;
    Spi_Send_Receive_Data(Power_Down);
    CS = 1;
    Wait_idle();                                                //等待空闲 
}

/**********************************************
*FUNC:唤醒Power_Down模式
*INSTRUCTION:发送Release_Power_Down即可
**********************************************/ 
void Release_Power_Down(void)
{
    CS = 0;
    Spi_Send_Receive_Data(Release_Power_Down);
    CS = 1;
    Wait_idle();                                                //等待空闲 
}

/**********************************************
*FUNC:读取设备ID
*INSTRUCTION:先发指令 再发三个无效字节 最后发送0xff接收一个字节的数据 即设备ID
**********************************************/ 
uint8_t Release_Power_Down(void)
{
    uint8_t dat;
    CS = 0;
    Spi_Send_Receive_Data(Release_Power_Down);
    Spi_Send_Receive_Data(0xff);
    Spi_Send_Receive_Data(0xff);
    Spi_Send_Receive_Data(0xff);
    dat = Spi_Send_Receive_Data(0xff);
    CS = 1;
    Wait_idle();                                               //等待空闲 
    return dat;
    
}

/***********************************************
*FUNC:读取芯片ID
*INSTRUCTION:发送指令90H+24位地址0x000000+芯片ID+制造商ID
***********************************************/ 
uint16_t Read_Chip_Id(void)
{
    uin16_t id = 0;
    CS = 0;
    Spi_Send_Receive_Data(Read_Chip_ID);
    Spi_Send_Receive_Data(0x00);
    Spi_Send_Receive_Data(0x00);
    Spi_Send_Receive_Data(0x00);
    id = Spi_Send_Receive_Data(0xff)<<8;
    id |= Spi_Send_Receive_Data(0xff);
    CS = 1;
    return id;
}




你可能感兴趣的:(柒:SPI总线,实现外部FLASH(W25Q128)读写)