• STC15单片机-通过EEPROM恢复PWM亮度


    通过EEPROM实现掉电后恢复PWM灯亮度

    EEPROM介绍

    EEPROM (Electrically Erasable Programmable Read-Only Memory),电可擦除可编程只读存储器,一种掉电后数据不丢失的存储芯片。

    EEPROM 可以在电脑上或专用设备上擦除已有信息,重新编程。一般用在即插即用

    EEPROM的发展

    ROM:

    微机的发展初期,BIOS都存放在ROM(Read Only Memory,只读存储器)中。ROM内部的资料是在ROM的制造工序中,在工厂里用特殊的方法被烧录进去的,其中的内容只能读不能改,一旦烧录进去,用户只能验证写入的资料是否正确,不能再作任何修改。如果发现资料有任何错误,则只有舍弃不用,重新订做一份。ROM是在生产线上生产的,由于成本高,一般只用在大批量应用的场合。

    PROM:

    由于ROM制造和升级的不便,后来人们发明了PROM(Programmable ROM,可编程ROM)。最初从工厂中制作完成的PROM内部并没有资料,用户可以用专用的编程器将自己的资料写入,但是这种机会只有一次,一旦写入后也无法修改,若是出了错误,已写入的芯片只能报废。PROM的特性和ROM相同,但是其成本比ROM高,而且写入资料的速度比ROM的量产速度要慢,一般只适用于少量需求的场合或是ROM量产前的验证。

    EPROM:

    EPROM(Erasable Programmable ROM,可擦除可编程ROM)芯片可重复擦除和写入,解决了PROM芯片只能写入一次的弊端。EPROM芯片有一个很明显的特征,在其正面的陶瓷封装上,开有一个玻璃窗口,透过该窗口,可以看到其内部的集成电路,紫外线透过该孔照射内部芯片就可以擦除其内的数据,完成芯片擦除的操作要用到EPROM擦除器。EPROM内资料的写入要用专用的编程器,并且往芯片中写内容时必须要加一定的编程电压(VPP=12~24V,随不同的芯片型号而定)。EPROM的型号是以27开头的,如27C020(8*256K)是一片2M Bits容量的EPROM芯片。EPROM芯片在写入资料后,还要以不透光的贴纸或胶布把窗口封住,以免受到周围的紫外线照射而使资料受损。

    EEPROM:

    由于EPROM操作的不便,后来出主板上BIOS ROM芯片大部分都采用EEPROM(Electrically Erasable Programmable ROM,电可擦除可编程ROM)。EEPROM的擦除不需要借助于其它设备,它是以电子信号来修改其内容的,而且是以Byte为最小修改单位,不必将资料全部洗掉才能写入,彻底摆脱了EPROM Eraser和编程器的束缚。EEPROM在写入数据时,仍要利用一定的编程电压,此时,只需用厂商提供的专用刷新程序就可以轻而易举地改写内容,所以,它属于双电压芯片。借助于EEPROM芯片的双电压特性,可以使BIOS具有良好的防毒功能,在升级时,把跳线开关打至“on”的位置,即给芯片加上相应的编程电压,就可以方便地升级;平时使用时,则把跳线开关打至“off”的位置,防止CIH类的病毒对BIOS芯片(BIOS是Basic Input Output System的缩写,意思是基本输入输出系统,是用于计算机开机过程中各种硬件设备的初始化和检测的芯片,容量是1M或2M甚至8M。)的非法修改。所以,仍有不少主板采用EEPROM作为BIOS芯片并作为自己主板的一大特色 。

    STC15系列单片机EEPROM的应用

    STC15系列单片机内部集成了大容量的EEPROM,其与程序空间是分开的。利用ISP/IAP技术可将内部Data Flash当EEPROM,擦写次数在10万次以上。EEPROM可分为若干个扇区,每个扇区包含512字节。使用时,建议同一次修改的数据放在同一个扇区,不是同一次修改的数据放在不同的扇区,不一定要用满。数据存储器的擦除操作是按扇区进行的。

    EEPROM可用于保存一些需要在应用过程中修改并且掉电不丢失的参数数据。在用户程序中,可以对EEPROM进行字节读/字节编程/扇区擦除操作。在工作电压VCC偏低时,建议不要进行EEPROM/IAP操作。

    IAP及EEPROM新增特殊功能寄存器介绍

    要使用到IAP对EEPROM进行操作,则需要配置以下的寄存器,除了PCON可以通过烧录软件STC-ISP进行选择而不需要编程配置,寄存器的详细用法可看数据手册

    在这里插入图片描述

    EEPROM扇区默认数据

    数据手册说到:

    1. 3个基本命令----字节读,字节编程,扇区擦除
    2. 字节编程:将“1”写成“1”或“0”,将“0”写成“0”。如果某字节是FFH,才可对其进行字节编程。如果该字节不是FFH,则须先将整个扇区擦除,因为只有“扇区擦除”才可以将“0”变为“1”。
    3. 扇区擦除:只有“扇区擦除”才可能将“0”擦除为“1”。

    所以扇区内如果从来没有被写入的话,默认的数据是FFH,因为只有为1,才能通过字节编程将其改变为1或0,就得到自己想写入的数据,如果本来是0的话,就只能被写成0,无法写入自己的数据;

    在写入数据之前,都要擦除一次扇区

    EEPROM空间大小及地址

    手上开发板的型号是STC15L2K32S2,字节数是29K,扇区数58个,起始地址:0000h,结束地址:73FFh

    数据手册中介绍完不同型号的EEPROM空间大小后,就有扇区的地址分布图

    在这里插入图片描述

    程序

    文件结构

    在这里插入图片描述

    main.c -> 主函数文件,包含 main 函数等;

    Public.c -> 公共函数文件,包含 Delay 延时函数等;

    Sys_init -> 系统初始化函数,包含 GPIO 初始化函数等;

    LED.c -> LED 外设函数,包含 LED 打开、关闭函数等;

    Timer0.c -> 定时器函数,包含定时器初始化,中断函数等;

    KEY1.c -> 按键 1 函数,包含按键检测,中断函数等;

    KEY2.c -> 按键 2 函数,包含按键状态机检测函数等;

    PWM.c -> PWM 初始化、亮度调节、占空比储存与恢复函数等;

    IAP.c -> 字节读、字节写、扇区擦除等函数。

    程序思路

    1.占空比备份检测到按键 2 有动作,调整占空比改变 PWM 灯亮度后,备份占空比。

    2.占空比恢复,亮度恢复上电进行 PWM 初始化时,恢复占空比,恢复亮度。

    IAP.h

    主要是宏定义要存储占空比的扇区地址,后面备份时用到的写入次数,以及结构体,包含IAP操作的函数指针

    #ifndef __IAP_H_
    #define __IAP_H_
    
    #define IAP_PWM_DUTY_ADDR (uint16_t)0x0000  //PWM占空比存储地址,使用第一个扇区
    
    #define IAP_CNT (uint8_t)10   //写入字节的最大次数
    
    //定义结构体类型
    typedef struct
    {
        uint8_t ucIAP_Flag;   //IAP操作标志位
        uint8_t ucIAP_Cnt;    //IAP操作计数
        uint8_t (*IapReadByte)(uint16_t );
        void    (*IapProgramByte)(uint16_t,uint8_t);
        void    (*IapEraseSector)(uint16_t);
    }IAP_t;
    
    /* extern variables-----------------------------------------------------------*/
    extern IAP_t IAP;
    /* extern function prototypes-------------------------------------------------*/ 
    
    #endif
    /********************************************************
      End Of File
    ********************************************************/
    
    • 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
    IAP.c

    通过IAP对EEPROM的操作,分别是读取一个字节,写入一个字节和擦除扇区,寄存器的参数根据数据手册来配置,数据手册这一章节的最后也有示例代码

    /* Includes ------------------------------------------------------------------*/
    #include 
    
    /* Private define-------------------------------------------------------------*/
    #define CMD_IDLE        0     //空闲、待机模式,无IAP操作
    #define CMD_READ        1     //IAP字节读命令
    #define CMD_PROGRAM     2     //IAP字节编程命令
    #define CMD_ERASE       3     //IAP扇区擦除命令
    #define ENABLE_IAP      0x83  //使能IAP,设置CPU等待时间
    /* Private variables----------------------------------------------------------*/
    static uint8_t IapReadByte(uint16_t addr);
    static void IapProgramByte(uint16_t addr,uint8_t dat);
    static void IapEraseSector(uint16_t addr);
    /* Public variables-----------------------------------------------------------*/
    IAP_t IAP = 
    {
      FALSE,
      0,
      IapReadByte,
      IapProgramByte,
      IapEraseSector
    };
    /* Private function prototypes------------------------------------------------*/
    
    /*
    * @name   IapIdle
    * @brief  关闭IAP
    * @param  None
    * @retval None   
    */
    static void IapIdle()
    {
      IAP_CONTR = 0;            //关闭IAP功能
      IAP_CMD   = CMD_IDLE;     //清除命令寄存器,MS1和MS0为0,待机模式,无ISP操作
      IAP_TRIG  = 0;            //清除触发寄存器
      IAP_ADDRH = 0xFF;         //将地址设置到非IAP地区
      IAP_ADDRL = 0xFF;
    }
    
    /*
    * @name   IapReadByte
    * @brief  读取一个字节
    * @param  addr:要读取字节的地址
    * @retval uint8_t:返回读到的字节数据
    */
    static uint8_t IapReadByte(uint16_t addr)
    {
      uint8_t dat;
      IAP_CONTR = ENABLE_IAP;       //使能IAP,最高位IAPEN置1,并设置CPU等待时间
      IAP_CMD   = CMD_READ;         //设置IAP命令,对EEPROM区进行字节读
      IAP_ADDRL = addr;             //设置IAP读取的低地址,IAP_ADDRL只会存储addr的低8位
      IAP_ADDRH = addr >> 8;        //设置IAP读取的高地址,将addr右移8位,则将低8位全部移出去了,剩下了高8位,赋给IAP_ADDRH
      IAP_TRIG  = 0x5a;             //按数据手册说法要写触发命令
      IAP_TRIG  = 0xa5;
      _nop_();                      //等待操作完成
      dat = IAP_DATA;               //读数据
      IapIdle();                    //关闭IAP功能,该函数可以不调用,只是出于安全考虑而使用,后面函数同理
      return dat;                   //返回读取到的数据
    }
    /*
    * @name   IapProgramByte
    * @brief  写入一个字节
    * @param  addr:要被写入的地址
    * @param  dat:写入的数据
    * @retval None   
    */
    static void IapProgramByte(uint16_t addr,uint8_t dat)
    {
      IAP_CONTR = ENABLE_IAP;       //使能IAP,最高位IAPEN置1,并设置CPU等待时间
      IAP_CMD   = CMD_PROGRAM;      //设置IAP命令,对EEPROM区进行字节编程
      IAP_ADDRL = addr;             //设置IAP写入的低地址
      IAP_ADDRH = addr >> 8;        //设置IAP写入到高地址
      IAP_DATA  = dat;              //写入数据
      IAP_TRIG  = 0x5a;             //写触发命令
      IAP_TRIG  = 0xa5;
      _nop_();                      //等待操作完成
      IapIdle();                    //关闭IAP功能
    }
    /*
    * @name   IapEraseSector
    * @brief  扇区擦除(每扇区为512字节)
    * @param  addr:要擦除的扇区地址
    * @retval None   
    */
    static void IapEraseSector(uint16_t addr)
    {
      IAP_CONTR = ENABLE_IAP;       //使能IAP,最高位IAPEN置1,并设置CPU等待时间
      IAP_CMD   = CMD_ERASE;        //设置IAP命令,对EEPROM区进行扇区擦除
      IAP_ADDRL = addr;             //设置IAP擦除扇区的低地址
      IAP_ADDRH = addr >> 8;        //设置IAP擦除扇区的高地址
      IAP_TRIG  = 0x5a;             //写触发命令
      IAP_TRIG  = 0xa5;         
      _nop_();                      //等待操作完成
      IapIdle();                    //关闭IAP功能
    }
    
    /********************************************************
      End Of File
    ********************************************************/
    
    • 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
    PWM.h

    在结构体内增加了两个函数指针,分别是通过IAP备份占空比和恢复占空比

    #ifndef __PWM_H_
    #define __PWM_H_
    
    //定义表示占空比的枚举类型
    typedef enum
    {
        Duty_0      = (uint8_t)0,
        Duty_20     = (uint8_t)20,
        Duty_40     = (uint8_t)40,
        Duty_60     = (uint8_t)60,
        Duty_80     = (uint8_t)80,
        Duty_100    = (uint8_t)100
    }PWM_Value_t;
    
    //定义结构体类型
    typedef struct 
    {
      PWM_Value_t Duty;                           //PWM占空比
      void (*PWM_Init)();                         //PWM初始化
      void (*PWM_LED_Adjust_Brightness)();        //调整PWM亮度
    
      void (*IAP_Duty_Backup)(uint16_t,uint8_t);   //通过IAP备份占空比
      uint8_t (*IAP_Duty_Restore)(uint16_t);       //通过IAP恢复占空比
    }PWM_t;
    
    /* extern variables-----------------------------------------------------------*/
    extern PWM_t PWM;
    /* extern function prototypes-------------------------------------------------*/ 
    
    #endif
    /********************************************************
      End Of File
    ********************************************************/
    
    • 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
    PWM.c

    将设置占空比的CCAP0H和CCAP0L赋值部分写成一个函数,在PWM初始化后和按键调整灯亮度函数最后调用该函数设置PWM;新增两个函数,分别是通过IAP备份占空比和恢复占空比,所用到的函数实现在IAP.c源文件中

    /* Includes ------------------------------------------------------------------*/
    #include 
    
    /* Private define-------------------------------------------------------------*/
    #define CCP_S1  BIT5
    #define CCP_S0  BIT4   
    
    #define EPC0H   BIT1
    #define EPC0L   BIT0
    /* Private variables----------------------------------------------------------*/
    static void PWM_Init();
    static void PWM_LED_Adjust_Brightness();
    
    static void PWM_Duty_Set(uint8_t PWM_Duty);
    
    static void IAP_Backup(uint16_t IAP_Addr,uint8_t IAP_Data);
    static uint8_t IAP_Restore(uint16_t IAP_Addr);
    /* Public variables-----------------------------------------------------------*/
    PWM_t PWM = 
    {
      Duty_20,
      PWM_Init,
      PWM_LED_Adjust_Brightness,
      /*初始化的函数名跟函数指针名不一样,这是因为这两个函数当作通用函数,其他文件也能使用,
      函数指针的名字仅拿来当PWM备份占空比使用*/
      IAP_Backup,               
      IAP_Restore
      };
    /* Private function prototypes------------------------------------------------*/
    
    /*
    * @name   PWM_Init
    * @brief  PWM初始化
    * @param  None
    * @retval None   
    */
    static void PWM_Init()
    {
        //选择管脚,因为开发板的PWM灯接到了P3.5口
        //将BIT5的CCP_S1清0,BIT4的CCP_S0置1,即可将CCP切换到P3.5管脚
        AUXR1 &= ~(CCP_S1); 
        AUXR1 |= (CCP_S0);
    
        //CCON里都是一些标志位,全置0即可
        /*CMOD 的BIT7置0,设置空闲模式下PAC计数器继续工作;BIT3、BIT2、BIT1置为110,
        系统时钟6分频,SYSclk/6*/
        CCON = 0x00;
        CMOD = 0x0C;
    
        //用于保存PCA装载值的16位计数器都清零
        CL = 0;
        CH = 0;
    
        //BIT6置1,允许比较器功能;BIT1置1,允许CCP0脚用作脉宽调节输出
        //PCA_PWM0的BIT7和BIT6置0,使模块工作于8位PWM模式,BIT1和BIT0的EPC0H和EPC0L清0
        CCAPM0 = 0x42;
        PCA_PWM0 = 0x00;
    
        //恢复占空比
        PWM.Duty = PWM.IAP_Duty_Restore(IAP_PWM_DUTY_ADDR);
    
        //设置占空比
        PWM_Duty_Set(PWM.Duty);
    		
        CR = 1;
    }
    
    /*
    * @name   PWM_LED_Adjust_Brightness
    * @brief  PWM灯调整亮度
    * @param  None
    * @retval None   
    */
    static void PWM_LED_Adjust_Brightness()
    {
        
        if(KEY2.KEY_Flag == TRUE)
        {
            //单击 亮度 0-20-40-60-80-100-0 循环调节
            //双击 亮度 100
            //长按 亮度 0
            if(KEY2.Click == TRUE)
            {
                switch (PWM.Duty)
                {
                  case Duty_0:    PWM.Duty = Duty_20; break;
                  case Duty_20:   PWM.Duty = Duty_40; break;
                  case Duty_40:   PWM.Duty = Duty_60; break;
                  case Duty_60:   PWM.Duty = Duty_80; break;
                  case Duty_80:   PWM.Duty = Duty_100;break;
                  case Duty_100:  PWM.Duty = Duty_0;  break;
                  default: PWM.Duty = Duty_0; break;
                }
            }
            //检测双击
            else if(KEY2.Double_Click == TRUE)
            {
              PWM.Duty = 100;
            }
            //检测长按
            else if(KEY2.Press == TRUE)
            {
              PWM.Duty = 0;
            }
            //设置占空比,调整亮度
            PWM_Duty_Set(PWM.Duty);
            
            //标志位清零
            KEY2.KEY_Flag     = FALSE;
            KEY2.Click        = FALSE;
            KEY2.Double_Click = FALSE;
            KEY2.Press        = FALSE;
    
            //备份占空比
            PWM.IAP_Duty_Backup(IAP_PWM_DUTY_ADDR,PWM.Duty);
        }
    }
    
    /*
    * @name   PWM_Duty_Set
    * @brief  PWM灯调整亮度
    * @param  None
    * @retval None   
    */
    static void PWM_Duty_Set(uint8_t PWM_Duty)
    {
      uint8_t  Temp_Value = 0;
      /*初始化时PWM.Duty的值为Duty_20,单击按下后,进入该switch语句
      PWM.Duty 被修改为Duty_40,占空比变量Temp_Value被赋值153,然后
      跳出switch语句,后面对CCAP0H赋值,输出占空比
      下次再单击按键,进入switch,匹配case Duty_40,所以PWM.Duty会
      再次被改变为Duty_60,占空比输出102即60%*/
      switch (PWM_Duty)
      {
        case Duty_0:    break;
        case Duty_20:   Temp_Value = 204; break;
        case Duty_40:   Temp_Value = 153; break;
        case Duty_60:   Temp_Value = 102; break;
        case Duty_80:   Temp_Value = 51;  break;
        case Duty_100:  break;
        default: PWM_Duty = Duty_0; break;
      }
      //亮度调节
      //占空比为0%,全输出低电平,PWM灯灭
      if(PWM.Duty == 0)
      {
        PCA_PWM0 |= (EPC0H);
        CCAP0H = 0xFF;
    
        PCA_PWM0 |= (EPC0L);		//置1则表示9位数,加上CCAP0L最大去到511
        CCAP0L = CCAP0H;
      }
      //占空比为100%,全输出高电平,PWM灯全亮
      else if(PWM.Duty == 100)
      {
        PCA_PWM0 &= ~(EPC0H);
        CCAP0H = 0x00;
        PCA_PWM0 &= ~(EPC0L);
        CCAP0L = 0x00;
      }
      else
      {
        //根据Temp_Value设置的值修改占空比
        PCA_PWM0 &= ~(EPC0H);	//~0000 0010  ->  1111 1101	即让EPC0H为0
        CCAP0H = Temp_Value;
        PCA_PWM0 &= ~(EPC0L);	//因为上一步将BIT0,即EPC0L位置1了,所以也让该位清0后,CCAP0H再赋值给CCAP0L
        CCAP0L = CCAP0H;
      }
    }
    
    /*
    * @name   IAP_Backup
    * @brief  通过IAP备份
    * @param  IAP_Addr:备份地址
    * @param  IAP_Data:备份数据
    * @retval None   
    */
    static void IAP_Backup(uint16_t IAP_Addr,uint8_t IAP_Data)
    {
      IAP.ucIAP_Cnt = IAP_CNT;  //写入的次数,多次写入都失败的话,单片机会死机的,所以要保证次数
    	
      while(IAP.ucIAP_Cnt--)
      {
        IAP.IapEraseSector(IAP_Addr);   //写数据前要把扇区擦除
        IAP.IapProgramByte(IAP_Addr,IAP_Data);    //写入数据
        IAP.ucIAP_Flag = TRUE;          //假如写成功了
        
        if(IAP.IapReadByte(IAP_Addr) != IAP_DATA)
        {
          IAP.ucIAP_Flag = FALSE;       //不成功
        }
        //判断是否写成功
        if(IAP.ucIAP_Flag == TRUE)    //若写成功,则退出循环
        {
          break;
        }
      }
    }
    
    /*
    * @name   IAP_Restore
    * @brief  通过IAP恢复
    * @param  IAP_Addr:恢复地址
    * @retval 恢复值   
    */
    static uint8_t IAP_Restore(uint16_t IAP_Addr)
    {
      //考虑产品稳定性,定义了两个变量,读两次
      uint16_t Read_IAP_Para1,Read_IAP_Para2;
      uint8_t IAP_Data = 0;
      
      //从EEPROM读取数据
      Read_IAP_Para1 = IAP.IapReadByte(IAP_Addr);
      Read_IAP_Para2 = IAP.IapReadByte(IAP_Addr);
    
      //判断两次读取是否一致
      if(Read_IAP_Para1 == Read_IAP_Para2)
      {
        if(Read_IAP_Para1 != 0xFF)  //如果读出来不是FF说明之前已经有备份
        {
          IAP_Data = Read_IAP_Para1;
        }
        else
        {
          IAP_DATA = Duty_0;        //从来就没备份写入过数据,就让灯灭
        }
      }
      //两次读取不一致
      else
      {
        IAP_Data = Duty_0;          //让PWM灯灭,也可以做其他处理,比如重启
      }
      return IAP_Data;
    }
    
    /********************************************************
      End Of File
    ********************************************************/
    
    • 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
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238

    数据手册中给出的要注意的细节

    在这里插入图片描述

    扇区擦除,没有字节擦除,只有扇区擦除,512字节/扇区,每个扇区用得越少越方便;

    如果要对某个扇区进行擦除,而其中有些字节的内容需要保留,则需将其先读到单片机内部的RAM中保存,再将该扇区擦除,然后将须保留的数据写回该扇区,所以每个扇区中用的字节数越少越好,操作起来越灵活方便;

    扇区中任意一个字节的地址都是该扇区的地址,无需求出首地址.

  • 相关阅读:
    Kubernetes安装nginx-controller作为统一网关
    【LeetCode】二叉树OJ
    【对列表中的每个元素做函数映射map()】
    Vue 面试题:了解 Vue 中的 Mixin 吗?
    java毕业设计基于web的学校工资管理系统Mybatis+系统+数据库+调试部署
    depthimage-to-laserscan
    [MAUI 项目实战] 手势控制音乐播放器(二): 手势交互
    HTMl案例二:注册页面
    go中的panic defer recover
    2023京东口腔护理赛道行业数据分析(京东销售数据分析)
  • 原文地址:https://blog.csdn.net/weixin_46251230/article/details/126680517