STM32F10X SPI操作flash MX25L64读写数据

发布者:幸福旅程最新更新时间:2017-02-07 来源: eefocus关键字:STM32F10X  SPI  操作flash  MX25L64  读写数据 手机看文章 扫描二维码
随时随地手机看文章

  简单的一种应用,ARM芯片作为master,flash为slaver,实现单对单通信。ARM主控芯片STM32F103,flash芯片为MACRONIX INTERNATIONAL的MX25L6465E,64Mbit。

  SPI应该是嵌入式外围中最简单的一种应用了吧!一般SPI应用有两种方法:软件仿真,手动模拟产生时序和应用主控芯片的SPI控制器。

  一般采用第二种方法比较好,比较稳定。应用主控芯片的SPI控制器,要点:正确的初始化SPI、操作SPI各寄存器和正确理解flash的时序。下面是过程,采用的是STM32F10X自带的库函数

 

1、初始化:void SpiFlashInitialzation(void);

  要知道硬件是怎么连接的,是SPI1还是SPI2连接到flash中去,通过连接图知道我们要操作的是SPI2。初始化大概3个部分,配置时钟;配置GPIO;配置SPI2。这里要注意的是,CS片选脚是作为普通的GPIO来使用,输出方式为“推挽式输出”,其他CLK,MISO,MOSI为“复用功能推挽式输出”;

代码:

void SpiFlashInitialzation(void)

{

     /*初始化的SPI,GPIO结构体*/

     SPI_InitTypeDef  SPI_InitStructure;

     GPIO_InitTypeDef GPIO_InitStructure;

     

     RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2,  ENABLE); /*在RCC_APB1ENB中使能SPI2时钟(位14)*/

     RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB,  ENABLE);/*因为与SPI2相关的4个引脚和GPIOB相*/

                                                                                                                        /*关,GPIOB时钟(位3),这句现在还不 */

                                                                                                                             /*确定要不要,待调试时再确定              */

    /*上面这一句是必须的,因为CS脚是当做GPIO来使用的,2011-01-30调试*/

                                                                                                                        

    /*配置SPI_FLASH_CLK(PB13),SPI_FLASH_MISO(PB14),SPI_FLASH_MOSI(PB15)*/

    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;

    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;                    /*复用功能推挽式输出*/

    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;

    GPIO_Init( GPIOB, &GPIO_InitStructure);

    

    /*配置输入SPI_FLASH_CS(PB12)*/

    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12;

    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;   /*推挽式输出*/

    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;

    GPIO_Init( GPIOB, &GPIO_InitStructure);


    SPI_FLASH_CS_SET;             /*不选flash*/



    /* SPI2配置 增加于2010-01-13*/

    /* 注意:  在SPI_NSS_Soft模式下,SSI位决定了NSS引脚上(PB12)的电平,

      *            而SSM=1时释放了NSS引脚,NSS引脚可以用作GPIO口*/

    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;   /*双线双向全双工BIDI MODE=0*/

    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                                 /*SSI位为1,MSTR位为1*/

    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                              /*SPI发送接收8位帧结构*/

    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;                                     /*CPOL=1,CPHA=1,模式3*/

    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;

    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                                          /*内部NSS信号由SSI位控制,SSM=1*/

    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;    /*波特率预分频值为4*/

    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                                       /*数据传输从MSB位开始*/

    SPI_InitStructure.SPI_CRCPolynomial = 7;                                                      /*复位默认值*/

    SPI_Init(SPI_SELECT, &SPI_InitStructure);


    SPI_Cmd(SPI_SELECT,ENABLE);       /*使能SPI2*/

    


}

2、正确的操作SPI控制器;

  这里需要注意的是理解SPI状态寄存器,特别是SPI_SR位7忙标志位BSY要小心,每次操作SPI要先读SPI_SR,BSY不忙才可下一步,然后就是操作缓冲器了。这里还有一个问题曾经困扰了我好久,SPI的时序问题,就是CLK怎么输出时序,最后我的理解是SPI每发送一个字节,CLK就自动会产生时序,如果没发送,CLK也就停止,这样节省了功耗。于是,如果SPI要接收字节,就必须先要发一个字节,例如发一个SPI_DUMMY_BYTE,Dummy byte有些flash有定义有些没有,没有的话自己随便定义一个,只要不和命令字相同就可以了。

u8 SpiFlashSendByte(u8 send_data);

u8 SpiFlashReceiveByte(void);

代码:

/*******************************2011-01-13******************************/

/*功能:       SPI发送一个字节

  *参数:       send_data:   待发送的字节

  *返回:       无*/

u8 SpiFlashSendByte(u8 send_data)

{

    /*检查Busy位,SPI的SR中的位7,SPI通信是否为忙,直到不忙跳出*/

    //while( SET==SPI_I2S_GetFlagStatus(SPI_SELECT, SPI_I2S_FLAG_BSY));

    

    /*检查TXE位,SPI的SR中的位1,发送缓冲器是否为空,直到空跳出*/

    while( RESET==SPI_I2S_GetFlagStatus(SPI_SELECT,SPI_I2S_FLAG_TXE));


    SPI_I2S_SendData(SPI_SELECT, send_data);                        /*发送一个字节*/

    

    /*发送数据后再接收一个字节*/

    while( RESET==SPI_I2S_GetFlagStatus(SPI_SELECT, SPI_I2S_FLAG_RXNE) );

    return( SPI_I2S_ReceiveData(SPI_SELECT) );

    

}


/*******************************2011-01-13******************************/

/*功能:       SPI接收flash的一个字节

  *参数:       接收到的字节

  *返回:       无*/

u8 SpiFlashReceiveByte(void)

{

    /*检查RXNE位,SPI的SR中位0,确定接收缓冲器是有数据的*/

    return(SpiFlashSendByte(SPI_DUMMY_BYTE));

}



3、理解flash的读写操作


  首先,写数据之前必须要擦除,因为所有的flash只能从1变为0,擦除将flash全部置1,写的时候相应位置0。


  读写操作这部分,flash芯片手册详细的说明了操作步骤,需要注意的是:flash MX25L64的状态寄存器。对flash操作之前,先读flash_SR,确保WIP=0(flash空闲),对flash擦除、编程等操作确保WEL=1(flash能够接受擦出编程等操作)。


  在对flash进行写操作时,要理解一点:对flash写数据(也就是Page Program(PP),Command 02)是基于页(256bytes)为单位的,如果数据写到页的末尾,会从当前页的首地址继续开始写剩余的数据,这样就有可能造成成数据的丢失,注意就可以了!主要是理解手册中的这段话:The Page Program(PP) instruction is for programming the memory to be "0"......If the eight least significant address bits(A7-A0) are not all 0,all transmitted data going beyond the end of the current page are grogrammed from the start address of the same page(from the address A7-A0 are all 0).If more than 256 bytes are sent to the device,the data of the last 256-byte is programmed at the requtest page and previous data will be disregarded. If less than 256 bytes .......


代码:


/*********************************2011-01-29*****************************/

/*功能:    在指定地址处开始从flash读取数据

    参数:     pData_from_flash,读取到的数据存放指针

                  address_to_read,  待读取的数据开始地址,地址格式有效位为:A23-A0

    返回:     指向读取到的数据指针pData_from_flash

  */

void SpiFlashReadData( u8 *pData_from_flash, u32 address_to_read , u16 size_to_read)

{

    /*先检查flash设备是否为忙,然后检查SPI控制器是否处于忙状态*/

    while( FLASH_SR_WIP==(SpiReadFlash_SR() & FLASH_SR_WIP) );/*读flash_SR*/

    while( SET==SPI_I2S_GetFlagStatus(SPI_SELECT, SPI_I2S_FLAG_BSY));/*读SPI_SR*/


    SPI_FLASH_CS_RESET;                                                                /*失能设备*/

        

    SpiFlashSendByte(SPI_COMMAND_READ);         /*发送读命令*/

    SpiFlashSendByte( (u8)((address_to_read & 0xFF0000) >> 16) );/*发送A23~A16*/

    SpiFlashSendByte( (u8)((address_to_read & 0xFF00) >> 8) );      /*发送A15~A8  */

    SpiFlashSendByte( (u8)(address_to_read & 0xFF) );                         /*发送A7~A0   */


    while( size_to_read>0 )

    {

        *pData_from_flash=SpiFlashReceiveByte();  /*读取数据*/

        pData_from_flash++;

        size_to_read--;

    }

        

    SPI_FLASH_CS_SET;

}


/*******************************2011-01-29******************************/

/*功能:     往指定地址处开始写数据

   *参数:     pBuff_to_write:       指向待写入的数据指针

   *              address_to_write:   flash何处开始写数据的地址

   *              size_to_write:          写入的数据字节数

   *返回:     TRUE:    写入成功

   *               FALSE:  写入失败

   *注意:     size_to_write,必须小于FLASH_PAGE_SIZE的大小(256 bytes),如果数据写到页

   *              的末尾,会从当前页的首地址0x00继续写剩余的数据,这样就造成数据的丢失,

   *              所以调用此函数得确保这一情况不会发生

   */

void SpiFlashWritePageData(u8 *pBuff_to_write,u32 address_to_write, u16 size_to_write)

{

    /*先检查flash设备是否为忙,然后检查SPI是否处于忙状态*/

    while( FLASH_SR_WIP==(SpiReadFlash_SR() & FLASH_SR_WIP) );/*flash_SR*/

    while( SET==SPI_I2S_GetFlagStatus(SPI_SELECT, SPI_I2S_FLAG_BSY));/*SPI_SR*/


     /*获得对flash的写权限*/

    while( FLASH_SR_WEL != (SpiReadFlash_SR() &FLASH_SR_WEL) )

    {

        SpiFlashWriteEnable();                                                       /*如果WEL为复位,则置位*/

    }


    SPI_FLASH_CS_RESET;

    SpiFlashSendByte(SPI_COMMAND_PP);                                                  /*发送写PP命令*/

    SpiFlashSendByte( (u8)((address_to_write & 0xFF0000) >> 16) );   /*发送A23~A16*/

    SpiFlashSendByte( (u8)((address_to_write & 0xFF00) >> 8) );         /*发送A15~A8  */

    SpiFlashSendByte( (u8)(address_to_write  & 0xFF) );                          /*发送A7~A0   */

    while( size_to_write>0 )

    {

        SpiFlashSendByte(*pBuff_to_write);

        pBuff_to_write++;

        size_to_write--;

    }

    SPI_FLASH_CS_SET;

        

    /*2011-01-14*/

    /*检查设备已经写完才退出*/

    while ( FLASH_SR_WIP==(SpiReadFlash_SR() & FLASH_SR_WIP) );/**/


}

 


4、  读写操作完成了,大概也就完成了,其它的参考flash手册就OK啦,不在描述。



另外,还有一种方法,是用软件模拟时序,这方法用在没有SPI控制器的单片机上很实用。


void SpiSendOneByte(u8 send_byte)

{

    _nop_();

    _nop_();

    //SPI_SCLK_RESET;


    /*第一个上升沿*/

    for( __IO u8  i=8; i>0; i-- )

    {

        SPI_SCLK_RESET;

        if( 0X00 != (send_byte & 0x80) )

        {

            SPI_MOSI_SET;

        }

        else

        {

            SPI_MOSI_RESET;

        }

        send_byte<<=1;

        SPI_SCLK_SET;

        _nop_();

        _nop_();

        _nop_();

    }

}


/*******************************************************************/

/*Serial Modes Supported(for Normal Serial mode)*/

/*                                    CPOL  CPHA

        Serial mode 0:           0          0

        Serial mode 3:           1          1

  */

  /*功能:  从高到低接收一个字节,高位先接收*/

  /*输出:  接收到的数据*/

  /*下降沿时,数据出现在SO,低电平的时候把数据读到*/

  u8 SpiGetOneByte(void)

{

    __IO u8 get_byte=0;


    for( __IO u8  i=0; i<8; i++ )

    {

        get_byte<<=1;

        SPI_SCLK_RESET;

        _nop_();

        _nop_();

        _nop_();

        _nop_();

        _nop_();

        

        if( 1==SPI_MISO )

        {

            get_byte |= SPI_MISO;

        }


        SPI_SCLK_SET;

        _nop_();

        _nop_();

        _nop_();

        

    }

    

    return(get_byte);

}


关键字:STM32F10X  SPI  操作flash  MX25L64  读写数据 引用地址:STM32F10X SPI操作flash MX25L64读写数据

上一篇:STM32-NVIC中断管理实现[直接操作寄存器]
下一篇:用DMA直接驱动GPIO,实现GPIO最高输出速率

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

SPI、I2C、UART三种串行总线的区别
SPI:Serial Peripheral Interface  串行外围接口 ISP:In Syesterm Program  在系统编程 AT89S52在系统编程(ISP)所用到的几个引脚定义:   SPI简述(Serial Peripheral Interface--串行外设接口)总线系统是一种同步串行外设接口,它可以使MCU与各种外围设备以串行方式进行通信以交换信息。外围设置FLASHRAM、网络控制器、LCD显示驱动器、A/D转换器和MCU等。SPI总线系统可直接与各个厂家生产的多种标准外围器件直接接口,该接口一般使用4条线:串行时钟线(SCK)、主机输入/从机输出数据线MISO、主机输出/从机输入数据
[嵌入式]
单片机 SPI 通信接口
UART、I2C 和 SPI 是单片机系统中最常用的三种通信协议。前边我们已经学了 UART 和 I2C 通信协议,这节课我们来学习剩下的 SPI 通信协议。 SPI 是英语 Serial Peripheral Interface 的缩写,顾名思义就是串行外围设备接口。SPI 是一种高速的、全双工、同步通信总线,标准的 SPI 也仅仅使用4个引脚,常用于单片机和 EEPROM、FLASH、实时时钟、数字信号处理器等器件的通信。SPI 通信原理比 I2C 要简单,它主要是主从方式通信,这种模式通常只有一个主机和一个或者多个从机,标准的 SPI 是4根线,分别是 SSEL(片选,也写作 SCS)、SCLK(时钟,也写作 SCK)、MO
[单片机]
单片机 <font color='red'>SPI</font> 通信接口
STM32—cubeMX+HAL库的SPI接口使用
本文主要介绍STM32的SPI接口、cubeMX软件配置SPI接口和分析SPI相关代码。 STM32之SPI简介: (1)SPI协议【Serial Peripheral Interface】 串行外围设备接口,是一种高速全双工的通信总线。主要用在MCU与FLASHADCLCD等模块之间的通信。 (2)SPI信号线 SPI 共包含 4 条总线。 SS(Slave Select):片选信号线,当有多个SPI 设备与 MCU 相连时,每个设备的这个片选信号线是与 MCU 单独的引脚相连的,而其他的 SCK、MOSI、MISO 线则为多个设备并联到相同的 SPI 总线上,低电平有效。 SCK (Serial C
[单片机]
STM32—cubeMX+HAL库的<font color='red'>SPI</font>接口使用
MSP430G2553 HC595 数码管 SPI 显示 时分秒
// Timer A0 interrupt service routine #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer_A(void) { t_count = (t_count + 1) % 500; //500次2ms就是1s if (t_count == 1) { rtc_sec++; if (rtc_sec == 60) { rtc_sec = 0; rtc_min++; if (rtc_min == 60) { rtc_min = 0;
[单片机]
MSP430G2553 HC595 数码管 <font color='red'>SPI</font> 显示 时分秒
B001-Atmega16-SPI Flash
主要内容: 第一步:SPI的结构 第二步:SPI的初始化 第三步:Atmega16的SPI自发自收 第四步:与SPI Flash连接(GD25Q32B) 第五步:读取SPI Flash的RDID(GD25Q32B) 第六步:SPI Flash操作接口(GD25Q32B) 第七步:SPI接口管理器 第八步:同时操作三个SPI Flas ------------------------------------------------------------------------------------------------------------------------------------- 开发环境:AVR Studio
[单片机]
B001-Atmega16-<font color='red'>SPI</font> <font color='red'>Flash</font>
stm32f105 spi 移植到stm32f103上
最近移植dw1000的程序,把stm32f105版本移植到103版本上去。 (1)发现两者的区别主要在于时钟上,具体请参考stm32 datasheet: 将始终改掉之后,SPI就可以运行stm32f103上了。 (2)PA15和PB3默认是用于JLINK的,需要启用复用时钟,代码如下: RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA ,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
[单片机]
stm32 spi 疑惑解疑 1
发送时 可以通过检测SPI_SR中的TXE位,当数据寄存器里有数据时,TXE位是0,当数据全部从数据寄存器的发送缓冲区传输到移位寄存器时TXE位被置1,这时候可以再往数据寄存器里写入数据。可以通过 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) 来检测。 SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE 是库函数可以检测SPI的一些状态位。 接收时 可以通过检测SPI_SR中的RXNE位,当数据寄存器里有数据时,RXNE位是0,当数据全部从数据寄存器的接收缓冲区传输到移位寄存器时RXNE位被置1,这时候可以从数
[单片机]
串行显示驱动器PS7219及单片机的SPI接口设计
0 引 言 在单片机的应用系统中,为了便于人们观察和监视单片机的运行情况,常常需要用显示器显示运行的中间结果及状态等等。因此显示器往往是单片机系统必不可少的外部设备之一。常用的显示器有很多种,其中LED(发光二极管显示器)是应用较多的一种,它特别适用于强光和光线极弱的场合。   要使LED显示,必须提供段选码和位选码。传统的硬件译码显示接口广泛采用由中央处理器CPU(如:Intel 8031)扩展I/O口(如:8255),然后再使用逻辑门驱动芯片(如7407等)驱动相应的位码和段码。这种设计,芯片间连线十分复杂,系统工作可靠性不高,已越来越不适应单片机系统集成化、小型化的发展要求。特别是系统并行扩展I/O,其缺点十分明显:
[应用]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

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

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

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