zoukankan      html  css  js  c++  java
  • SPI操作flash MX25L64读写数据

    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为“复用功能推挽式输出”;

    代码:

    [c-sharp] view plain copy
     
    1. void SpiFlashInitialzation(void)  
    2. {  
    3.      /*初始化的SPI,GPIO结构体*/  
    4.      SPI_InitTypeDef  SPI_InitStructure;  
    5.      GPIO_InitTypeDef GPIO_InitStructure;  
    6.        
    7.      RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2,  ENABLE); /*在RCC_APB1ENB中使能SPI2时钟(位14)*/  
    8.      RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB,  ENABLE);/*因为与SPI2相关的4个引脚和GPIOB相*/  
    9.                                                                                                                         /*关,GPIOB时钟(位3),这句现在还不 */  
    10.                                                                                                                              /*确定要不要,待调试时再确定              */  
    11.     /*上面这一句是必须的,因为CS脚是当做GPIO来使用的,2011-01-30调试*/  
    12.                                                                                                                           
    13.     /*配置SPI_FLASH_CLK(PB13),SPI_FLASH_MISO(PB14),SPI_FLASH_MOSI(PB15)*/  
    14.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;  
    15.     GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;                    /*复用功能推挽式输出*/  
    16.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  
    17.     GPIO_Init( GPIOB, &GPIO_InitStructure);  
    18.       
    19.     /*配置输入SPI_FLASH_CS(PB12)*/  
    20.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12;  
    21.     GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;   /*推挽式输出*/  
    22.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  
    23.     GPIO_Init( GPIOB, &GPIO_InitStructure);  
    24.   
    25.     SPI_FLASH_CS_SET;             /*不选flash*/  
    26.   
    27.   
    28.     /* SPI2配置 增加于2010-01-13*/  
    29.     /* 注意:  在SPI_NSS_Soft模式下,SSI位决定了NSS引脚上(PB12)的电平, 
    30.       *            而SSM=1时释放了NSS引脚,NSS引脚可以用作GPIO口*/  
    31.     SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;   /*双线双向全双工BIDI MODE=0*/  
    32.     SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                                 /*SSI位为1,MSTR位为1*/  
    33.     SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                              /*SPI发送接收8位帧结构*/  
    34.     SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;                                     /*CPOL=1,CPHA=1,模式3*/  
    35.     SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;  
    36.     SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                                          /*内部NSS信号由SSI位控制,SSM=1*/  
    37.     SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;    /*波特率预分频值为4*/  
    38.     SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                                       /*数据传输从MSB位开始*/  
    39.     SPI_InitStructure.SPI_CRCPolynomial = 7;                                                      /*复位默认值*/  
    40.     SPI_Init(SPI_SELECT, &SPI_InitStructure);  
    41.   
    42.     SPI_Cmd(SPI_SELECT,ENABLE);       /*使能SPI2*/  
    43.       
    44.   
    45. }  

    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);

     代码:

    [cpp] view plain copy
     
    1. /*******************************2011-01-13******************************/  
    2. /*功能:       SPI发送一个字节 
    3.   *参数:       send_data:   待发送的字节 
    4.   *返回:       无*/  
    5. u8 SpiFlashSendByte(u8 send_data)  
    6. {  
    7.     /*检查Busy位,SPI的SR中的位7,SPI通信是否为忙,直到不忙跳出*/  
    8.     //while( SET==SPI_I2S_GetFlagStatus(SPI_SELECT, SPI_I2S_FLAG_BSY));  
    9.       
    10.     /*检查TXE位,SPI的SR中的位1,发送缓冲器是否为空,直到空跳出*/  
    11.     while( RESET==SPI_I2S_GetFlagStatus(SPI_SELECT,SPI_I2S_FLAG_TXE));  
    12.   
    13.     SPI_I2S_SendData(SPI_SELECT, send_data);                        /*发送一个字节*/  
    14.       
    15.     /*发送数据后再接收一个字节*/  
    16.     while( RESET==SPI_I2S_GetFlagStatus(SPI_SELECT, SPI_I2S_FLAG_RXNE) );  
    17.     return( SPI_I2S_ReceiveData(SPI_SELECT) );  
    18.       
    19. }  
    [cpp] view plain copy
     
    1. /*******************************2011-01-13******************************/  
    2. /*功能:       SPI接收flash的一个字节 
    3.   *参数:       接收到的字节 
    4.   *返回:       无*/  
    5. u8 SpiFlashReceiveByte(void)  
    6. {  
    7.     /*检查RXNE位,SPI的SR中位0,确定接收缓冲器是有数据的*/  
    8.     return(SpiFlashSendByte(SPI_DUMMY_BYTE));  
    9. }  

      

    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 .......

    代码:

    [cpp] view plain copy
     
    1. /*********************************2011-01-29*****************************/  
    2. /*功能:    在指定地址处开始从flash读取数据 
    3.     参数:     pData_from_flash,读取到的数据存放指针 
    4.                   address_to_read,  待读取的数据开始地址,地址格式有效位为:A23-A0 
    5.     返回:     指向读取到的数据指针pData_from_flash 
    6.   */  
    7. void SpiFlashReadData( u8 *pData_from_flash, u32 address_to_read , u16 size_to_read)  
    8. {  
    9.     /*先检查flash设备是否为忙,然后检查SPI控制器是否处于忙状态*/  
    10.     while( FLASH_SR_WIP==(SpiReadFlash_SR() & FLASH_SR_WIP) );/*读flash_SR*/  
    11.     while( SET==SPI_I2S_GetFlagStatus(SPI_SELECT, SPI_I2S_FLAG_BSY));/*读SPI_SR*/  
    12.   
    13.     SPI_FLASH_CS_RESET;                                                                /*失能设备*/  
    14.           
    15.     SpiFlashSendByte(SPI_COMMAND_READ);         /*发送读命令*/  
    16.     SpiFlashSendByte( (u8)((address_to_read & 0xFF0000) >> 16) );/*发送A23~A16*/  
    17.     SpiFlashSendByte( (u8)((address_to_read & 0xFF00) >> 8) );      /*发送A15~A8  */  
    18.     SpiFlashSendByte( (u8)(address_to_read & 0xFF) );                         /*发送A7~A0   */  
    19.   
    20.     while( size_to_read>0 )  
    21.     {  
    22.         *pData_from_flash=SpiFlashReceiveByte();  /*读取数据*/  
    23.         pData_from_flash++;  
    24.         size_to_read--;  
    25.     }  
    26.           
    27.     SPI_FLASH_CS_SET;  
    28. }  
    [c-sharp] view plain copy
     
    1. /*******************************2011-01-29******************************/  
    2. /*功能:     往指定地址处开始写数据 
    3.    *参数:     pBuff_to_write:       指向待写入的数据指针 
    4.    *              address_to_write:   flash何处开始写数据的地址 
    5.    *              size_to_write:          写入的数据字节数 
    6.    *返回:     TRUE:    写入成功 
    7.    *               FALSE:  写入失败 
    8.    *注意:     size_to_write,必须小于FLASH_PAGE_SIZE的大小(256 bytes),如果数据写到页 
    9.    *              的末尾,会从当前页的首地址0x00继续写剩余的数据,这样就造成数据的丢失, 
    10.    *              所以调用此函数得确保这一情况不会发生 
    11.    */  
    12. void SpiFlashWritePageData(u8 *pBuff_to_write,u32 address_to_write, u16 size_to_write)  
    13. {  
    14.     /*先检查flash设备是否为忙,然后检查SPI是否处于忙状态*/  
    15.     while( FLASH_SR_WIP==(SpiReadFlash_SR() & FLASH_SR_WIP) );/*flash_SR*/  
    16.     while( SET==SPI_I2S_GetFlagStatus(SPI_SELECT, SPI_I2S_FLAG_BSY));/*SPI_SR*/  
    17.   
    18.      /*获得对flash的写权限*/  
    19.     while( FLASH_SR_WEL != (SpiReadFlash_SR() &FLASH_SR_WEL) )  
    20.     {  
    21.         SpiFlashWriteEnable();                                                       /*如果WEL为复位,则置位*/  
    22.     }  
    23.   
    24.     SPI_FLASH_CS_RESET;  
    25.     SpiFlashSendByte(SPI_COMMAND_PP);                                                  /*发送写PP命令*/  
    26.     SpiFlashSendByte( (u8)((address_to_write & 0xFF0000) >> 16) );   /*发送A23~A16*/  
    27.     SpiFlashSendByte( (u8)((address_to_write & 0xFF00) >> 8) );         /*发送A15~A8  */  
    28.     SpiFlashSendByte( (u8)(address_to_write  & 0xFF) );                          /*发送A7~A0   */  
    29.     while( size_to_write>0 )  
    30.     {  
    31.         SpiFlashSendByte(*pBuff_to_write);  
    32.         pBuff_to_write++;  
    33.         size_to_write--;  
    34.     }  
    35.     SPI_FLASH_CS_SET;  
    36.           
    37.     /*2011-01-14*/  
    38.     /*检查设备已经写完才退出*/  
    39.     while ( FLASH_SR_WIP==(SpiReadFlash_SR() & FLASH_SR_WIP) );/**/  
    40.   
    41. }  

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

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

    [c-sharp] view plain copy
     
    1. void SpiSendOneByte(u8 send_byte)  
    2. {  
    3.     _nop_();  
    4.     _nop_();  
    5.     //SPI_SCLK_RESET;  
    6.   
    7.     /*第一个上升沿*/  
    8.     for( __IO u8  i=8; i>0; i-- )  
    9.     {  
    10.         SPI_SCLK_RESET;  
    11.         if( 0X00 != (send_byte & 0x80) )  
    12.         {  
    13.             SPI_MOSI_SET;  
    14.         }  
    15.         else  
    16.         {  
    17.             SPI_MOSI_RESET;  
    18.         }  
    19.         send_byte<<=1;  
    20.         SPI_SCLK_SET;  
    21.         _nop_();  
    22.         _nop_();  
    23.         _nop_();  
    24.     }  
    25. }  
    [cpp] view plain copy
     
    1. /*******************************************************************/  
    2. /*Serial Modes Supported(for Normal Serial mode)*/  
    3. /*                                    CPOL  CPHA 
    4.         Serial mode 0:           0          0 
    5.         Serial mode 3:           1          1 
    6.   */  
    7.   /*功能:  从高到低接收一个字节,高位先接收*/  
    8.   /*输出:  接收到的数据*/  
    9.   /*下降沿时,数据出现在SO,低电平的时候把数据读到*/  
    10.   u8 SpiGetOneByte(void)  
    11. {  
    12.     __IO u8 get_byte=0;  
    13.   
    14.     for( __IO u8  i=0; i<8; i++ )  
    15.     {  
    16.         get_byte<<=1;  
    17.         SPI_SCLK_RESET;  
    18.         _nop_();  
    19.         _nop_();  
    20.         _nop_();  
    21.         _nop_();  
    22.         _nop_();  
    23.           
    24.         if( 1==SPI_MISO )  
    25.         {  
    26.             get_byte |= SPI_MISO;  
    27.         }  
    28.   
    29.         SPI_SCLK_SET;  
    30.         _nop_();  
    31.         _nop_();  
    32.         _nop_();  
    33.           
    34.     }  
    35.       
    36.     return(get_byte);  
    37. }  
  • 相关阅读:
    如何通过命令行窗口查看sqlite数据库文件
    eclipse自动补全的设置
    文本装饰
    注释和特殊符号
    文本装饰
    网页背景
    通过ArcGIS Server admin 查看和删除已注册的 Web Adaptor
    通过 ArcGIS Server Manager 查看已安装的 Web Adaptor
    通过 ArcGIS Server Manager 验证 DataStore
    Windows上安装ArcGIS Enterprise——以 Windows Server 2012 R2上安装 ArcGIS 10.8为例
  • 原文地址:https://www.cnblogs.com/jiangzhaowei/p/7838844.html
Copyright © 2011-2022 走看看