• 嵌入式开发--赛普拉斯cypress的铁电存储器FM25CL64B


    嵌入式开发–赛普拉斯cypress的铁电存储器FM25CL64B

    简介

    FM25CL64B是赛普拉斯cypress出品的一款铁电存储器,这种存储器最大的优势是可以像RAM一样随机存储,和按字节写入,也可以像ROM一样掉电仍然可以保存数据,是一种相当优秀的新型存储器,但是容量不能做得很大,只适合保存一些重要数据。

    重要参数及解读如下:

    64K位,即8K字节
    100T的读写次数,这意味着即使对于同一单元,每毫秒读取或写入一次,也需要3170年才能消耗完这个次数,而我们对存储器的访问几乎不可能达到这样的频率,也不可能只访问一个数据单元,所以它的读写次数寿命虽然有限,但可以不在考虑之列。
    151年的数据保存周期,除了极其特殊用途,基本上是足够的了,毕竟我们之前用的光盘和现在FLASH,数据保存周期也才10年而已。
    SPI接口,频率支持到20M,可直接硬件替换SPI口的EEPROM,SPI支持MODO_0 (CPOL = 0, CPHA = 0)即时钟空闲时为低,第1个跳变沿采样数据;和MODO_3(CPOL = 1, CPHA = 1)即时钟空闲时为高,第2个跳变沿采样数据。看手册是自动识别,但我没有测试。
    受保护以避免写入的区域为:1/4或1/2或全片保护可选
    8脚的SOP或DFN封装
    -40~85度温度

    芯片框图

    封装
    在这里插入图片描述

    内部工作框图:
    在这里插入图片描述

    硬件电路图

    在这里插入图片描述
    我接的是MCU的SP1口,如下:
    在这里插入图片描述

    CubeMX初始化设置

    PA15设置为GPIO输出,速度设为High,由软件控制这个片选引脚。
    在这里插入图片描述
    Motorola格式,16位数据,MSB,CPOL和CPHA参照之前的说明,CRC校验关闭,软件片选。
    软件片选的原因是ST的SPI接口中,NSS不是通常意义上的片选, 一旦开启SPI,这个引脚就只能为低,不能为高。而且,如果SPI总线上还有其他设备,也只能用软件片选。
    需要注意的是Baud Rate这里,不能超过20M

    芯片操作

    控制码

    控制码共有6个,如下图
    在这里插入图片描述
    分别对应的是
    WREN 写使能
    WRDI 写禁止
    RDSR 读状态寄存器
    WRSR 写状态寄存器
    READ 读操作
    WRITE 写操作

    其他操作无意义或者说不开放,后面会讲到。

    写使能

    这是8位命令。
    在写入数据之前需要先进行写使能操作,否则无法写入
    同时,在SO数据线上,无数据响应,也就是说写使能是单向操作,芯片没有回应。
    具体时序如下:
    在这里插入图片描述

    写禁止WRDI

    这是8位命令。
    执行本命令后,对芯片的写操作无效。
    同样的,在执行本命令时,SO无回应。
    在这里插入图片描述

    8位操作码

    对于前面2个8位操作码,由于我们在CubeMX中,将SPI设置为16位数据,那么无法完成对8位操作码,即1字节的操作。
    例如:当发送WRDI(0x04)时,解决方案有3个:
    1 发送重复的操作指令,发送0x0404来实现,即2次同样的操作。
    2 发送0x0004,或0x0400,由于0x00不是操作码,芯片不会对其响应。
    3 发送8位操作码时,修改SPI的寄存器,以实现8位操作。发送完成后再改为16位模式。
    我用的是方案1,实测工作正常,就不再折腾寄存器了。

    读状态寄存器

    16位操作命令
    前8位是操作码RDSR(0x05),后8位SI可以没有任何数据,有也不影响。
    同时,在后8位时,SO会有数据输出,结束后在SPI的DR寄存器可以看到结果,也就是状态寄存器SR的数据。
    如果此时SO上没有观察到波形,说明配置有错误。
    在这里插入图片描述

    写状态寄存器

    16位操作命令,前8位是操作码0x01,后8位是需要写入状态寄存器的值。
    SO线没有波形输出。
    在这里插入图片描述

    写保护

    需要注意的是,向SR写入数据时,读取的不一定和写入相同。这与写保护的相关设置有关联,而且只有BIT1,2,3,7位是有效位,其他的位读出来永远是0。
    具体请查看写保护的相关部分,我是向状态寄存器写0,然后读到的值应该是0x02,也就是说开启写操作,而且状态寄存器和存储空间均不保护,处于可写的状态。
    写入完成后,将WEL位设置为0即可,如下图是相关的位,与保护区域的表格。
    在这里插入图片描述

    读数据和写数据

    这两个操作类似,就放到一块说了。
    这是32位操作码,
    开始的8位,是操作码
    接下来的8位,是地址码的高8位,由于本芯片是64K位,即8K字节,也就是说,地址码的有效位是3+8=11位,所以本区段 只有3个数据位是有效位。当然对于不同容量的芯片来说,有效位数是不一样的。
    第3个8位,是地址码的低8位
    最后8位,是数据位,写数据时,在SI线上有波形,读数据时,在SO上有波形。
    在这里插入图片描述

    HOLD引脚

    HOLD引脚是用来暂停当前操作的,必须在时钟信号为低期间,HOLD引脚才能为低,
    此时时钟,SI这两根线可以任意变化
    需要退出HOLD状态前,时钟必须为低,SO可任意。
    当HOLD拉高后,继续之前的操作。
    个人感觉这个功能不太实用,只有当软件模拟SPI时才用得上。硬件SPI时,有折腾时序的时间,SPI的活都干完了
    不用这个功能的话,本引脚接高即可。

    WP引脚

    硬件写保护,提供对状态寄存器的保护。
    该引脚为高时,禁止对状态寄存器写入。可防止误操作。
    不用这个功能的话,本引脚接高即可。

    写入次数分析

    以下是对同一个数据单元进行重复读写的寿命评估。
    第1列是SPI时钟频率
    第2列是每秒可以访问的次数
    第3列是每年的访问次数
    第4列是可以不间断写入多少年
    在这里插入图片描述
    可以看出,即使以本芯片所能支持的最高速度20MHz,来不间断的对同一数据单元进行读写访问,仍然可以维持85年,这个寿命是妥妥的够了。
    更何况我们不可能总是读写同一个数据单元,而且不可能以如此高的频率进行数据访问。
    所以芯片的寿命是可以放心的。

    相关代码

    LL_FRAM.h文件的代码

    #ifndef __LL_FRAM_H__
    #define __LL_FRAM_H__
    
    
    #include "LL_define.h"
    #include "spi.h"
    
    //状态寄存器   X表示无意义,默认值是0
    //  BIT7      BIT6      BIT5      BIT4      BIT3      BIT2      BIT1      BIT0
    //  WPEN      X(0)      X(0)      X(0)      BP1       BP0       WEL       X(0)
    //  WEL:      1为写使能,0为禁止写
    //  BP0,BP1:  块保护
    //  WPEN:     
    
    #define FRAM_WREN   0x06    //写入使能,即设置WEL位       在写操作(WRSR和WRITE)之前,必须先发本命令
    #define FRAM_WRDI   0x04    //写入禁止,即清除WEL位       本操作会禁止写操作(WRSR和WRITE),此后即使发送(WRSR和WRITE)也无效
    #define FRAM_RDSR   0x05    //读状态寄存器
    #define FRAM_WRSR   0x01    //写状态寄存器,前序操作为FRAM_WREN
    #define FRAM_READ   0x03    //读
    #define FRAM_WRITE  0x02    //写,前序操作为FRAM_WREN
    
    
    #define SPI1_NSS(n)  (n?HAL_GPIO_WritePin(SPI1_NSS_GPIO_Port, SPI1_NSS_Pin, GPIO_PIN_SET):HAL_GPIO_WritePin(SPI1_NSS_GPIO_Port, SPI1_NSS_Pin, GPIO_PIN_RESET))
    
    
    extern u8 spi_read_data;  //spi读取的数值
    
    
    u8 LL_fram_read_sr(void);
    u8 LL_fram_write_sr(u8 value);
    u8 LL_fram_read(u16 addr, u8 ret_data);
    u8 LL_fram_write(u16 addr, u8 value);
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    LL_FRAM.c文件的代码

    #include "LL_FRAM.h"
    
    u8 spi_read_data=0;
    u8 spi_tx_data[10] = {0x06};
    
    
    u8 LL_fram_read_sr(void)  //读状态寄存器,其返回值是状态寄存器的内容
    {
      u8 data[4];
      u8 ret;
      
      //写使能
      data[0] = FRAM_WREN;
      data[1] = FRAM_WREN;
      SPI1_NSS(0);
      HAL_SPI_Transmit(&hspi1, data, 1, 100);
      SPI1_NSS(1);
    
      //读状态寄存器,必须先有写使能操作FRAM_WREN  0x05
      data[0] = 0;
      data[1] = FRAM_RDSR;
      SPI1_NSS(0);
      HAL_SPI_TransmitReceive(&hspi1, data, &ret, 1, 10);
      SPI1_NSS(1);
      
      return ret;
    }
    
    
    u8 LL_fram_write_sr(u8 value)   //写状态寄存器,其函数的参数是向状态寄存器写入的内容
    {
      u8 data[4];
      u8 ret;
      
     
      //写使能
      data[0] = FRAM_WREN;
      data[1] = FRAM_WREN;
      SPI1_NSS(0);
      HAL_SPI_Transmit(&hspi1, data, 1, 100);
      SPI1_NSS(1);
    
    
      //写状态寄存器    0x01
      data[0] = value;
      data[1] = FRAM_WRSR;
      SPI1_NSS(0);
      HAL_SPI_TransmitReceive(&hspi1, data, &spi_read_data, 1, 10);
      SPI1_NSS(1);
    
      return 0;
    }
    
    
    //读操作
    //addr:读取地址
    //ret_data: 读取的数据保存在此处
    u8 LL_fram_read(u16 addr, u8 ret_data)
    {
      u8 data[4];
      u8 ret;
      u8 state;
    
      //读存储器    0x02
      data[0] = addr>>8;
      data[1] = FRAM_READ;
      data[2] = 0x00;
      data[3] = (u8)(addr&0x00ff);
      
      SPI1_NSS(0);
      state = HAL_SPI_TransmitReceive(&hspi1, data, &spi_read_data, 2, 20);   //(&hspi1, data, &ret, 2, 10);
      SPI1_NSS(1);
    
      if(state == 0)
      {
        spi_read_data = (u8)hspi1.Instance->DR;
        ret = 0;
      }
      else
      {
        ret = state;
      }
      return ret;
    }
    
    
    //写操作
    //addr:写入地址
    //ret_data: 写入的数据
    u8 LL_fram_write(u16 addr, u8 value)
    {
      u8 data[4];
      u8 ret;
      
      //写使能
      data[0] = FRAM_WREN;
      data[1] = FRAM_WREN;
      SPI1_NSS(0);
      HAL_SPI_Transmit(&hspi1, data, 1, 100);
      SPI1_NSS(1);
    
    
      //写存储器    0x02
      data[0] = addr>>8;
      data[1] = FRAM_WRITE;
      data[2] = value;
      data[3] = addr&0xff;
      SPI1_NSS(0);
      HAL_SPI_TransmitReceive(&hspi1, data, &ret, 2, 10);
      SPI1_NSS(1);
      
      return ret;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114

    测试读写

      while(1)
      {
        LL_fram_write_sr(0x000);    //取消保护,以准备写入
        if(LL_fram_read_sr() == 0x02)         //读取状态寄存器,应该是0x02
        {
          for(i=0; i<8*1024; i++)
          {
            LL_fram_write(i,(u8)i);
            j++;
            temp = LL_fram_read(i, spi_read_data);
            if(temp == 0)
            {
              if(spi_read_data != (u8)i)
              {
                //读出的数据与写入的数据不相等
                LED_ERR(1);
                goto begin;
              }
            }
            else
            {
              //LL_fram_read()函数返回值不是0,表示运行出错,DR寄存器内的数据不能保证正确性
              LED_RUN(1);
              goto begin;
              j++;
            }
          }
        }
      }
    
    begin: 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    这一段函数不停的执行写入和读取操作,并进行比对。
    如果SPI操作异常,则亮起LED_RUN灯,并跳转到begin处,
    如果读取的结果,和写入的数据比对错误,则亮起LED_ERR灯,并跳转到begin处。
    经长时间运行(2小时以上),发现正常运行时工作正常,未出现错误跳转。
    带上仿真器全速运行时,会不时跳转到LED_RUN(1)处,也就是说SPI函数的运行不正确,显然是由于连接了仿真器造成的。但是不会跳转到LED_ERR(1)处,也就是说不会出现读取的数据不正确的情况。

    总结

    为了保证数据正确,应当判断LL_fram_read()函数的返回值,应当是0,此时再去读取数据才是正确的,否则会有错误的风险。

  • 相关阅读:
    【NVMe2.0b 14-2】Create/Delete Queue
    推进高校学生党建工作数字化建设的思考
    Python学习(3)-基础语法(字典,函数)
    机器学习基础介绍
    [机器学习] 监督学习和无监督学习
    部分依赖图(Partial Dependence Plots)以及实战-疾病引起原因解释
    记 2022年11月5日 信息安全工程师考试
    React Native for Arcgis 地图开发 MeasureCtrl (十六)
    第六节:数组的定义与使用【java】
    搭建jenkins一键部署java项目
  • 原文地址:https://blog.csdn.net/13011803189/article/details/134486867