STM32 内部FLASH详解

news2024/9/20 13:07:31

目录

STM32 内部FLASH详解

1. STM32 FLASH简介

2. STM32 FLASH与SRAM

3. STM32 FLASH 容量、内容介绍

4. STM32 FLASH 读写注意事项

5. STM32 FLASH 基本结构

6. STM32 FLASH 读写步骤

6.1 FLASH 解除或添加 读、写保护的方法

6.2 FLASH 如何使用指针 读写存储器的方法

6.3 FLASH 闪存 全 擦除 时 过程

6.4 FLASH 闪存 页 擦除 时 过程

6.5 FLASH 闪存 写入 时 过程

7. STM32 选项字节 的组织和用途

8. STM32 选项字节 的擦除和编程

擦除 选项字节时 过程

编程 选项字节时 过程

9 . STM32 FLASH 器件的电子签名

10. STM32 ST-LINK Utility调试工具的使用

11. KEIL FLASH 下载程序 需要注意的设置

11.1 下载程序起始位置。划定范围

11.2 下载程序 时 对FLASH擦除选项

11.3 查看程序占用大小

12. STM32 flash.h介绍

flash.h中三个部分的介绍

适用于所有stm32F10x设备的函数(第一部分)函数介绍

13. 编写:读写内部FLASH

11.1 工程目标

10.1 工程结构

MyFLASH.c

MyFLASH.h

Store.c

Store.h

main.c

14. 编写:读写内部ID

main.c


STM32 内部FLASH详解

1. STM32 FLASH简介

  • STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分,通过闪存存储器接口(外设)可以对程序存储器和选项字节进行擦除和编程 (系统存储器是用来存放原厂写入的用于串口下载的BootLoader的。不允许我们进行修改)
  • 读写FLASH的用途:
    1. 利用程序存储器的剩余空间来保存掉电不丢失的用户数据
    2. 通过在程序中编程(IAP),实现程序的自我更新 (直接修改程序本身,不避开程序,与OTA类似)
  • 在线编程(In-Circuit Programming – ICP)用于更新程序存储器的全部内容,它通过JTAG、SWD协议或系统加载程序(Bootloader)下载程序 (也就是我们平时用的下载程序的方式 )
  • 在程序中编程(In-Application Programming – IAP)可以使用微控制器支持的任一种通信接口下载程序 (也就是自己写一个BootLoader程序,放到程序不会覆盖到的地方。在需要升级时,让程序跳转到自己写的BootLoader中。根据自己的 协议,比如蓝牙,串口,WIFI等 控制FLASH读写。覆盖原有的程序。这其实也是系统的BootLoader一样)

2. STM32 FLASH与SRAM

  1. ROM的存储介质是FLASH、RAM的存储介质是SRAM
  2. FLASH 闪存掉电不丢失、SRAM掉电丢失
  3. 闪存主要有程序存储器、系统存储器、选项字节三个部分。
    • 其中程序存储器是空间最大最主要的部分。所以也乘坐主存储器。起始地址为0x0800 0000 ,是用来存储编译后的代码 所以我们平时所说的FLASH容量,往往指的是FLASH中程序存储器的容量
    • 系统存储器起始地址为0x 1FFF F000 ,用于存储BootLoader ,用于串口下载
    • 选项字节其实地址为0x1FFF F800 ,用于存储一些独立的配置参数
  4. SRAM区域分为运行内存SRAM、外设寄存器、内核外设寄存器
    • SRAM运行内存的起始地址是0x2000 0000 , 用于存储运行过程的临时变量
    • 外设寄存器的起始地址是0x4000 0000 , 用于存储各个外设的配置参数
    • 内核外设寄存器 的起始地址是0xE000 0000 , 用于存储内核各个外设的配置参数

3. STM32 FLASH 容量、内容介绍

  1. FLASH在 STM32中根据不同的型号,容量也不同。

    • 以stm32F10x系列为例
    • 小容量产品:32页 ,每页1K
    • 中容量产品:128页,每页1K
    • 大容量产品:256页,每页2K
  2. 以中容量为例讲解:

    这个图中,把FLASH分为了三个块:

    • 主存储器(程序存储器):用来存放编译后的程序
    • 信息快: 又可以分为两个
      1. 启动程序代码 (系统存储器): 存放原厂写入的BootLoader,用于串口下载
      2. 用户选择字节(选项字节) :用于存放一些独立的参数
    • 闪存存储器接口寄存器:这个的地址是40开头的 ,根据SRAM的地址分配可以看到。闪存存储器接口寄存器是一个外设,与GPIO、定时器、串口等是一个性质的东西。 闪存存储器接口可以理解为上述FLASH闪存的管理员。是用来控制闪存的擦除和编程的

    对于F103 C8T6主存储器有0-63页,共64页,也就是64K

    • 地址的规律:只要是000、400、800、C00 结尾的。就是页的起始地址。
    1. 启动程序代码(系统存储器)占用了1K空间,地址为0x1FFF F000
    2. 用户选择字节(选项字节):只有16个字节的配置参数

    闪存存储器接口寄存器,每个寄存器占4个字节。

4. STM32 FLASH 读写注意事项

  1. 通过闪存存储器接口(外设)我们可以对程序存储器和选项字节进行擦除和编程。 **但系统存储器是不可以修改的。**它是用来存放原厂写入的用于串口下载的BootLoader的。

  2. 在选取FLASH存储区域时,一定不要覆盖原有的程序。不然就运行不了了。

  3. 所有的FLASH闪存 的写入和擦除规定了:

    • 写入前必须擦除
    • 擦除必须以最小单位进行(这里为页 1K)
    • 擦除后数据位全变为1
    • 数据只能1写0 ,不能0写1
    • 擦除和写入需要等待忙
  4. 在写入数据时,如果指定地址没有被擦除,那么就不会执行编程。同时提出警告(例外是写入0000,这样不会出问题。)

  5. 在写入数据时,如果指定地址为写保护状态,那么也不会执行编程。同时提出警告。

  6. 在整片擦除时,信息块不受影响(系统存储器和选项字节)

  7. 在编程过程中(BSY为1)时,任何读写闪存的操作都会使CPU暂停。直到此次读写结束。

    这时读写内部闪存存储数据的一个弊端。在闪存忙的时候,代码执行会暂停。

    会导致 在读写内部闪存的时候,中断响应不及时等对时间要求比较严格的。

  8. 对选项字节来编程的时候:WRP0位都是反逻辑位:0为实施写保护,1为取消写保护(因为闪存擦除之后都是1,1 是默认的)

  9. 善用STM32 ST-LINK Utility 调试工具

5. STM32 FLASH 基本结构

以C8T6为例

6. STM32 FLASH 读写步骤

6.1 FLASH 解除或添加 读、写保护的方法

FLASH需要再写入之前解除写保护。这里的操作方式和独立看门狗一样。是通过键寄存器写入特定的键值来实现。可以防止误操作

  • FPEC共有三个键值:
    1. RDPRT键 (解除保护)= 0x000000A5
    2. KEY1 (解除保护1)= 0x45670123
    3. KEY2 (解除保护2)= 0xCDEF89AB
  • 解锁:
    1. 复位后,FPEC被保护,不能写入FLASH_CR(默认是锁的)
    2. 在FLASH_KEYR先写入KEY1,再写入KEY2,解锁
    3. 错误的操作序列会在下次复位前锁死FPEC和FLASH_CR
  • 加锁:
    1. 设置FLASH_CR中的LOCK位(写1)锁住FPEC和FLASH_CR

6.2 FLASH 如何使用指针 读写存储器的方法

使用指针读指定地址下的存储器:

  • uint16_t Data = *((__IO uint16_t *)(0x08000000));

使用指针写指定地址下的存储器:

  • *((__IO uint16_t *)(0x08000000)) = 0x1234;

其中: #define __IO volatile __IO就是 volatile

是C语言中易变的数据,这是一个安全保障措施。

  1. 一能防止编译器优化,(连续对某个变量赋值,或者执行空循环 会在开启优化时被优化)
  2. 二能告诉编译器,这个变量是个易变的数据。每次读取都要到位,直接从内存中找,不要去缓存中读取。

6.3 FLASH 闪存 全 擦除 时 过程

  1. 读取LOCK位,看看芯片是否被锁,
    • 如果锁住,就执行解锁过程(在KEYR寄存器先写入KEY1,再写入KEY2)
    • 如果解锁,就置控制寄存器的MER(Mass Erase 大规模擦除)再置STRT(Start 开始)为1
  2. 判断状态控制器BSY(Busy 忙)是否为1 , 如果为1 就继续判断。等待他忙完(BSY = 0)跳出循环
  3. 最后一步验证一般不管。

6.4 FLASH 闪存 页 擦除 时 过程

  1. 读取LOCK位,看看芯片是否被锁,
    • 如果锁住,就执行解锁过程(在KEYR寄存器先写入KEY1,再写入KEY2)
    • 如果解锁,就置控制寄存器的PER(Page Erase 大规模擦除)、 ****然后在AR(Address Register 地址寄存器)选择要擦除的页。 最后置STRT(Start 开始)为1
  2. 判断状态控制器BSY(Busy 忙)是否为1 , 如果为1 就继续判断。等待他忙完(BSY = 0)跳出循环
  3. 最后一步验证一般不管。

6.5 FLASH 闪存 写入 时 过程

STM32的闪存在写入之前会检查指定地址有没有擦除。如果没有擦除就写入, STM32则不执行写入操作(除非写入的数据全是0)

  1. 读取LOCK位,看看芯片是否被锁,
    • 如果锁住,就执行解锁过程(在KEYR寄存器先写入KEY1,再写入KEY2)
    • 如果解锁,就置控制寄存器的PG**(Programming** 程序编制)表示我们即将写入数据、
  2. 在指定的地址写入半字(16位):*((__IO uint16_t *)(0x08000000)) = 0x1234; (不需要置STRT了)
  3. 判断状态控制器BSY(Busy 忙)是否为1 , 如果为1 就继续判断。等待他忙完(BSY = 0)跳出循环
  4. 最后一步验证一般不管。

7. STM32 选项字节 的组织和用途

选项字节存储的区域只有16个字节。起始地址为0x1FFF F800

其中有一半的字节前边都带了个n(比如USER和nUSER……)

这个的意思是,在写入USER时,要同时写入nUSER的反码..其他都是一样

只有芯片检测到这两个芯片是反码的关系才会执行相对应的功能。

这是一个安全保障措施(硬件会自动计算反码并填入)

每个存储器的功能

  • RDP:写入RDPRT键(0x000000A5)后解除读保护
  • USER:配置硬件看门狗和进入停机/待机模式是否产生复位
  • Data0/1:用户可自定义使用
  • WRP0/1/2/3:配置写保护,每一个位对应保护4个存储页(中容量)
    • WRP0位都是反逻辑位:0为实施写保护,1为取消写保护(因为闪存擦除之后都是1,1 是默认的)

    • ( (小容量产品32K)也是每位保护4页,所以只需要WRP0一个字节就够了)

      ( (大容量产品512K)每位保护2页,但是WRP3的位7直接剩下把所有页(62~255页)全部保护)

8. STM32 选项字节 的擦除和编程

选项字节本身也是闪存,所以在写入前也要擦除。流程和程序存储器类似,但细节有些出入

擦除 选项字节时 过程

  1. 解锁FLASH闪存 (手册中没写,但一定要打开 这个相当于 门口大锁)
  2. 检查FLASH_SR的BSY位,以确认没有其他正在进行的闪存操作 (事前等待)
  3. 解锁FLASH_CR的OPTWRE (Option Write Enable 选项写入使能)位 (相当于卧室小锁)
  4. 设置FLASH_CR的OPTER(Option Erase 选项清除)位为1 (即将擦除选项字节)
  5. 设置FLASH_CR的STRT(Start 开始)位为1 (触发芯片开始干活)
  6. 等待BSY位变为0 (等待忙完)
  7. 读出被擦除的选择字节并做验证

编程 选项字节时 过程

  1. 解锁FLASH闪存 (手册中没写,但一定要打开 这个相当于 门口大锁)
  2. 检查FLASH_SR的BSY位,以确认没有其他正在进行的闪存操作 (事前等待)
  3. 解锁FLASH_CR的OPTWRE (Option Write Enable 选项写入使能)位 (相当于卧室小锁)
  4. 设置FLASH_CR的OPTPG(Option Programming 选项编程)位为1 (即将开始编程写入)
  5. 写入要编程的半字(16位)到指定的地址
  6. 等待BSY位变为0 (等待忙完)
  7. 读出被擦除的选择字节并做验证

9 . STM32 FLASH 器件的电子签名

电子签名存放在闪存存储器模块的系统存储区域(就是BootLoader哪里),包含的芯片识别信息在出厂时编写,不可更改,使用指针读指定地址下的存储器可获取电子签名

闪存容量寄存器:(显示闪存容量) 基地址:0x1FFF F7E0 大小:16位

产品唯一身份标识寄存器:( 可以用来作为序列号、防被盗、激活秘钥) 基地址: 0x1FFF F7E8 大小:96位 可以以字节、半字、字的方式读取

10. STM32 ST-LINK Utility调试工具的使用

  1. 连接
    • 插入好ST link之后点击连接(不能在stm32休眠模式下连接)
  2. 退出
    • 在使用完ST-LINK Utility调试工具后要及时断开,不然KEIl下载不了程序
  3. 查看程序数据
    • 连接后,下面窗口中就是闪存中的数据了
    • 可以指定地址 查看数据、指定查看范围、指定以什么类型去看(字节、半字、字、)

  1. 查看选项字节配置

    • 进入
    • 功能介绍

11. KEIL FLASH 下载程序 需要注意的设置

11.1 下载程序起始位置。划定范围

在下载程序时,我们可以选定FLASH和RAM的起始位置。这样下载程序就可以指定位置下载。并且可以限制最大到那个位置,

比如要下载一个自己写的BootLoader。就可以指定到页尾去下载。

或者程序的最后几页,打算存储数据用,那么就可以通过划定程序的下载范围,不让下载时覆盖掉自己想保存的数据。 当然,如果程序过大,你划的范围幼小,那么会下载失败。

11.2 下载程序 时 对FLASH擦除选项

一般使用用多少擦除多少。下载速度快且不会动后面的程序

11.3 查看程序占用大小

在程序编译之后,会有一段:Program Size :……

前三个数为占用FLASH程序存储的大小, 后俩数 是占用SRAM的大小

也可以在Target处直接双击,在.map文件拖到最后边。也有大小显示。

12. STM32 flash.h介绍

flash.h中三个部分的介绍

如下是flash.h中的函数声明

可以看到在flash.h中可以看到有三块区域。分别对应了

  • 这些是所有F10x设备机都可以使用的函数
  • 所有设备机都可以使用的、新的函数
  • 只有XL加大容量的设备才可以使用的、新的函数(有个预编译,定义了宏才有效)

他们的产生原因是:

  • 期初在stm32中,最初 只有小容量LD、中容量MD、大容量HD

  • 之后,加大容量XL才推出。XL是添加了一块新的、独立的闪存。即有两块

    所以设计者命名新加的一块交Bank2 。与之对应,原来的小中大容量的那一块叫做Bank1

  • 在加大容量系列出来之后,对flash.c .h中的函数 进行了适配和更新(具体更改可以在.c中查看)

适用于所有stm32F10x设备的函数(第一部分)函数介绍

和内核运行代码有关,不需要过多了解

void FLASH_SetLatency(uint32_t FLASH_Latency);

void FLASH_HalfCycleAccessCmd(uint32_t FLASH_HalfCycleAccess);

void FLASH_PrefetchBufferCmd(uint32_t FLASH_PrefetchBuffer);

void FLASH_Unlock(void);

  • 解锁

void FLASH_Lock(void);

  • 加锁

FLASH_Status FLASH_ErasePage(uint32_t Page_Address);

  • 闪存擦除某一页(返回值为完成状态)

FLASH_Status FLASH_EraseAllPages(void);

  • 闪存全擦除(返回值为完成状态)

FLASH_Status FLASH_EraseOptionBytes(void);

  • 擦除选项字节(返回值为完成状态)

FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);

  • 在指定地址写入字

FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);

  • 在指定地址写入半字

FLASH_Status FLASH_ProgramOptionByteData(uint32_t Address, uint8_t Data);

  • 选项字节:自定义Data 0 、 1

FLASH_Status FLASH_EnableWriteProtection(uint32_t FLASH_Pages);

  • 写保护

FLASH_Status FLASH_ReadOutProtection(FunctionalState NewState);

  • 读保护

FLASH_Status FLASH_UserOptionByteConfig(uint16_t OB_IWDG, uint16_t OB_STOP, uint16_t OB_STDBY);

  • 用户选项的三个配置位

uint32_t FLASH_GetUserOptionByte(void);

  • 获取用户选项的三个配置位

uint32_t FLASH_GetWriteProtectionOptionByte(void);

  • 获取写保护状态

FlagStatus FLASH_GetReadOutProtectionStatus(void);

  • 获取读保护状态

FlagStatus FLASH_GetPrefetchBufferStatus(void);

  • 获取预取缓冲区状态

void FLASH_ITConfig(uint32_t FLASH_IT, FunctionalState NewState);FlagStatus

  • 中断使能

FLASH_GetFlagStatus(uint32_t FLASH_FLAG);

  • 获取标志位

void FLASH_ClearFlag(uint32_t FLASH_FLAG);

  • 清除标志位

FLASH_Status FLASH_GetStatus(void);

  • 获取状态

FLASH_Status FLASH_WaitForLastOperation(uint32_t Timeout);

  • 等待上一次操作(等待BSY) 函数内部会自动调用,并不需要我们单独调用

13. 编写:读写内部FLASH

11.1 工程目标

在闪存最后一页进行读写。

  • 为了方便读写,提高效率。在SRAM中定义数组来对数组操作,通过函数间接控制FLASH。
  • SRAM在每次更改时,都把自己以整体备份到闪存中
  • 在每次上电时,把闪存中的 数据初始化加载到SRAM数组中
  • 另外为了判断此闪存是否之前保存过数据,使用页的第一个半字来存放标志位。如果标志位有,那么上电就直接加载闪存数据到SRAM中就可以了。如果不是就把标志位放进去,然后初始化闪存,再把闪存的数据搬运到SRAM中

10.1 工程结构

  • MyFLASH.c :实现闪存最基本的三个功能:读取、擦除、编程
  • Store.c :实现对数据的读写和存储管理:定义SRAM数组,把SRAM数组自动备份到FLASH里。复位、上电后,闪存数据会自动读回道SRAM。
  • main.c : 测试读写内部FLASH

MyFLASH.c

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:FLASH读取一个32位的字
  * 参    数:Address 要读取数据的字地址
  * 返 回 值:指定地址下的数据
  */
uint32_t MyFLASH_ReadWord(uint32_t Address)
{
    return *((__IO uint32_t *)(Address));	//使用指针访问指定地址下的数据并返回
}

/**
  * 函    数:FLASH读取一个16位的半字
  * 参    数:Address 要读取数据的半字地址
  * 返 回 值:指定地址下的数据
  */
uint16_t MyFLASH_ReadHalfWord(uint32_t Address)
{
    return *((__IO uint16_t *)(Address));	//使用指针访问指定地址下的数据并返回
}

/**
  * 函    数:FLASH读取一个8位的字节
  * 参    数:Address 要读取数据的字节地址
  * 返 回 值:指定地址下的数据
  */
uint8_t MyFLASH_ReadByte(uint32_t Address)
{
    return *((__IO uint8_t *)(Address));	//使用指针访问指定地址下的数据并返回
}

/**
  * 函    数:FLASH全擦除
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,FLASH的所有页都会被擦除,包括程序文件本身,擦除后,程序将不复存在
  */
void MyFLASH_EraseAllPages(void)
{
    FLASH_Unlock();                 //解锁
    FLASH_EraseAllPages();          //全擦除
    FLASH_Lock();                   //加锁
}

/**
  * 函    数:FLASH页擦除
  * 参    数:PageAddress 要擦除页的页地址
  * 返 回 值:无
  */
void MyFLASH_ErasePage(uint32_t PageAddress)
{
    FLASH_Unlock();                 //解锁
    FLASH_ErasePage(PageAddress);   //页擦除
    FLASH_Lock();                   //加锁
}

/**
  * 函    数:FLASH编程字
  * 参    数:Address 要写入数据的字地址
  * 参    数:Data 要写入的32位数据
  * 返 回 值:无
  */
void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data)
{
    FLASH_Unlock();                         //解锁
    FLASH_ProgramWord(Address, Data);       //编程字
    FLASH_Lock();                           //加锁
}

/**
  * 函    数:FLASH编程半字
  * 参    数:Address 要写入数据的半字地址
  * 参    数:Data 要写入的16位数据
  * 返 回 值:无
  */
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
{
    FLASH_Unlock();                         //解锁
    FLASH_ProgramHalfWord(Address, Data);   //编程半字
    FLASH_Lock();                           //加锁
}

MyFLASH.h

#ifndef __MYFLASH_H
#define __MYFLASH_H

//读出字
uint32_t MyFLASH_ReadWord(uint32_t Address);
//读出半字
uint16_t MyFLASH_ReadHalfWord(uint32_t Address);
//读出字节
uint8_t MyFLASH_ReadByte(uint32_t Address);

//清除所有页
void MyFLASH_EraseAllPages(void);
//清除某页
void MyFLASH_ErasePage(uint32_t PageAddress);

//编写字
void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data);
//编写半字
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);

#endif

Store.c

#include "stm32f10x.h"                  // Device header
#include "MyFLASH.h"

#define STORE_START_ADDRESS     0x0800FC00      //存储的起始地址
#define STORE_COUNT             512             //存储数据的个数

uint16_t Store_Data[STORE_COUNT];               //定义SRAM数组

/**
  * 函    数:参数存储模块初始化
  * 参    数:无
  * 返 回 值:无
  */
void Store_Init(void)
{
    /*判断是不是第一次使用*/
    if (MyFLASH_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5)    //读取第一个半字的标志位,if成立,则执行第一次使用的初始化
    {
        MyFLASH_ErasePage(STORE_START_ADDRESS);                 //擦除指定页
        MyFLASH_ProgramHalfWord(STORE_START_ADDRESS, 0xA5A5);   //在第一个半字写入自己规定的标志位,用于判断是不是第一次使用
        
        for (uint16_t i = 1; i < STORE_COUNT; i ++)             //循环STORE_COUNT次,除了第一个标志位
        {
            MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, 0x0000);       //除了标志位的有效数据全部清0
        }
    }
    
    /*上电时,将闪存数据加载回SRAM数组,实现SRAM数组的掉电不丢失*/
    for (uint16_t i = 0; i < STORE_COUNT; i ++)             //循环STORE_COUNT次,包括第一个标志位
    {
        Store_Data[i] = MyFLASH_ReadHalfWord(STORE_START_ADDRESS + i * 2);      //将闪存的数据加载回SRAM数组
    }
}

/**
  * 函    数:SRAM数组保存数据到FLASH闪存
  * 参    数:无
  * 返 回 值:无
  */
void Store_Save(void)
{
    MyFLASH_ErasePage(STORE_START_ADDRESS);             //擦除指定页
    
    for (uint16_t i = 0; i < STORE_COUNT; i ++)         //循环STORE_COUNT次,包括第一个标志位
    {
        MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, Store_Data[i]);    //将SRAM数组的数据备份保存到闪存
    }
}

/**
  * 函    数:SRAM数组清零,再同步到FLASH闪存
  * 参    数:无
  * 返 回 值:无
  */
void Store_Clear(void)
{
    for (uint16_t i = 1; i < STORE_COUNT; i ++)         //循环STORE_COUNT次,除了第一个标志位
    {       
        Store_Data[i] = 0x0000;                         //SRAM数组有效数据清0
    }                       
    
    Store_Save();                                       //保存数据到闪存
}

Store.h

#ifndef __STORE_H
#define __STORE_H

//对外声明保存最后一页的SRAM数组
extern uint16_t Store_Data[];

//初始化
void Store_Init(void);
//把SRAM数组写入FLASH
void Store_Save(void);
//清除FLASH(除了标志位)
void Store_Clear(void);

#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Store.h"
#include "Key.h"

int main(void)
{
    /*模块初始化*/
    OLED_Init();                //OLED初始化
    KEY_Init();                //按键初始化
    Store_Init();               //参数存储模块初始化,在上电的时候将闪存的数据加载回Store_Data,实现掉电不丢失
    
    /*显示静态字符串*/
    OLED_ShowString(1, 1, "Flag:");
    OLED_ShowString(2, 1, "Data:");
    
    while (1)
    {
        uint8_t Key_Num = KEY_Get();    //记录按下的键   
        if (Key_Num == 1)               //按键1按下
        {
            Store_Data[1] ++;           //变换测试数据
            Store_Data[2] += 2;
            Store_Data[3] += 3;
            Store_Data[4] += 4;
            Store_Save();               //将Store_Data的数据备份保存到闪存,实现掉电不丢失
        }
        
        else if (Key_Num == 2)          //按键2按下
        {
            Store_Clear();              //将Store_Data的数据全部清0
        }
        
        OLED_ShowHexNum(1, 6, Store_Data[0], 4);    //显示Store_Data的第一位标志位
        
        OLED_ShowHexNum(3, 1, Store_Data[1], 4);    //显示Store_Data的有效存储数据
        OLED_ShowHexNum(3, 6, Store_Data[2], 4);
        OLED_ShowHexNum(4, 1, Store_Data[3], 4);
        OLED_ShowHexNum(4, 6, Store_Data[4], 4);
    }
}

14. 编写:读写内部ID

在13的基础上 只修改了main.c

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Store.h"

int main(void)
{   
    //0x1FFFF7E0为FLASH ID 寄存器位置,。是一个96位的
    
    OLED_Init();                        //OLED初始化
    
    OLED_ShowString(1, 1, "F_SIZE:");   //显示容量:
    
    OLED_ShowHexNum(1, 8, *((__IO uint16_t *)(0x1FFFF7E0)), 4);     //使用指针读取指定地址下的 闪存容量寄存器
    
    OLED_ShowString(2, 1, "U_ID:");     //显示芯片ID:
    
    //使用指针读取指定地址下的产品唯一身份标识寄存器
    //可以由字节、半字、字的方式读取
    OLED_ShowHexNum(2, 6, *((__IO uint8_t *)(0x1FFFF7E8)), 2);              //字节读出:第一个字节        (0-8位)
    OLED_ShowHexNum(2, 9, *((__IO uint8_t *)(0x1FFFF7E8) + 1), 2);          //字节读出:第二个字节        (9-16位)
    OLED_ShowHexNum(2, 12, *((__IO uint16_t *)(0x1FFFF7E8) + 1), 4);        //半字读出:第3 、4 个字节    (16-32位)
    OLED_ShowHexNum(3, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 1)), 8);      //字  读出:5 6 7 8个字节     (33-64位)
    OLED_ShowHexNum(4, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 2)), 8);      //字  读出:9 10 11 12个字节  (64-96位)
    
    while (1)
    {

    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2058009.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java | Leetcode Java题解之第354题俄罗斯套娃信封问题

题目&#xff1a; 题解&#xff1a; class Solution {public int maxEnvelopes(int[][] envelopes) {if (envelopes.length 0) {return 0;}int n envelopes.length;Arrays.sort(envelopes, new Comparator<int[]>() {public int compare(int[] e1, int[] e2) {if (e1[…

JMeter的安装和使用

&#x1f4a5; 该系列属于【SpringBoot基础】专栏&#xff0c;如您需查看其他SpringBoot相关文章&#xff0c;请您点击左边的连接 目录 一、安装 1. 下载 2. 解压 3. 修改配置 4. 运行 二、使用 1. 添加线程组 2. 添加http取样器 3. 添加监听报告 4. 添加监听结果树 …

SpringBoot依赖之Spring Data Redis实现位图Bitmap

Spring Boot 项目中使用 Spring Data Redis 实现位图Bitmap 暂未发表&#xff0c;记录于20240820 概念 Spring Data Redis (AccessDriver) 依赖名称: Spring Data Redis (AccessDriver)功能描述: Advanced and thread-safe Java Redis client for synchronous, asynchronous,…

Python(PyTorch)物理变化可微分神经算法

&#x1f3af;要点 &#x1f3af;使用受控物理变换序列实现可训练分层物理计算 | &#x1f3af;多模机械振荡、非线性电子振荡器和光学二次谐波生成神经算法验证 | &#x1f3af;训练输入数据&#xff0c;物理系统变换产生输出和可微分数字模型估计损失的梯度 | &#x1f3af;…

ubuntu命令大全

查看系统版本 lsb_release -a

C++模板方法TemplateMethod

23种设计模式分为九类 1.组件协作 2.单一职责 3.对象创建 4.对象性能 5.接口隔离 6.状态变化 7.数据结构 8.行为变化 9.领域问题 什么时候、什么地点用设计模式 才是最重要的。 关键的重构技法&#xff1a; 静态-----动态 早绑定—晚绑定 继承-----组合 编译时依赖------运行…

计算机毕业设计--基于深度学习(PSPNet、空洞卷积Atrous Convolutions)的多类型图像通用分割模型

基于深度学习(PSPNet、空洞卷积Atrous Convolutions)的多类型图像通用分割模型 更多基于深度学习的毕业设计请关注专栏 --- 计算机毕业设计 ✨ 动物图分割&#xff08;使用训练集DIS5K-TR&#xff0c;DIS-TEs&#xff0c;DUTS-TR_TE &#xff09; ✨自然与人类图像分割&#xf…

支持最新 mysql9的workbench8.0.39 中文汉化教程来了

之前在 B 站上发布了 mysql8 workbench 汉化教程&#xff0c;一年多来帮助很多初学者解决了不熟悉英文的烦恼。 汉化视频可以访问&#xff1a; 2024最新版mysql8.0.39中文版mysql workbench汉化 中文升级 旧版汉化报错解决_哔哩哔哩_bilibili MySql Workbench汉化_哔哩哔哩_bi…

C++ 左值引用与右值引用超详解

目录 一 左值与右值 1.左值 2.右值 3.总结 二 左值引用与右值引用 1.左值引用 2.右值引用 3.总结与探究 3.1右值引用可以修改么&#xff1f;取地址么&#xff1f; 3.2左值引用与右值引用转化 左值引用 引用 右值 右值引用 引用 左值 3.3左值引用与右值引用相同之处 3.4左…

MySQL基础:函数

&#x1f48e;所属专栏&#xff1a;MySQL 函数是指一段可以直接被另一段程序调用的程序或代码&#xff0c;在MySQL中也内置了许多函数供开发者去调用&#xff0c;例如之前提到的聚合函数&#xff0c;本节再去介绍一些其他常用的函数 字符串函数 函数功能CONCAT(S1,S2...Sn)字…

开源的量化交易领域平台vn.py(VeighNa)

一&#xff1a;vn.py&#xff08;VeighNa&#xff09;下的工具以及社区版和Elite版的区别 vn.py是一款广泛应用于量化交易领域的开源软件&#xff0c;它主要有以下用途和功能&#xff1a; 1. 交易系统开发框架&#xff1a;vn.py提供了一个完整的交易系统开发框架&#xff0c;可…

桶排序算法及优化(java)

目录 1.1 引言 1.2 桶排序的历史 1.3 桶排序的基本原理 1.3.1 工作流程 1.3.2 关键步骤 1.4 桶排序的Java实现 1.4.1 简单实现 1.4.2 优化实现 1.4.3 代码解释 1.5 桶排序的时间复杂度 1.5.1 分析 1.5.2 证明 1.6 桶排序的稳定性 1.7 著名案例 1.7.1 应用场景 …

基于GPT-SoVITS的API实现批量克隆声音

目标是将每一段声音通过GPT-SoVITS的API的API进行克隆,因为拼在一起的整个片段处理会造成内存或者缓存溢出。 将目录下的音频文件生成到指定目录下,然后再进行拼接。 通过AI工具箱生成的数据文件是这样的结构,temp目录下是没个片段生成的部分,connect_是正常拼接的音频文件…

笨鸟先飞(疯狂的小鸟)小游戏自制分享

《Flappy Bird》是一款由越南独立游戏开发者阮哈东&#xff08;Dong Nguyen&#xff09;制作并发布的移动端小游戏。该游戏最初于2013年上线&#xff0c;在2014年初迅速走红&#xff0c;成为全球范围内的热门现象。 游戏的玩法非常简单&#xff0c;玩家只需通过点击屏幕来控制…

Python | Leetcode Python题解之第355题设计推特

题目&#xff1a; 题解&#xff1a; class Twitter:class Node:def __init__(self):self.followee set()self.tweet list()def __init__(self):self.time 0self.recentMax 10self.tweetTime dict()self.user dict()def postTweet(self, userId: int, tweetId: int) ->…

基于人工智能、三维视觉、混合现实等技术的智慧能源开源了

一、简介 AI视频监控平台, 是一款功能强大且简单易用的实时算法视频监控系统。愿景在最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;减少企业级应用约 95%的开发成本&#xff0c;在强大视频算…

AI学习记录 - LSTM详细拆解

拒绝熬夜&#xff0c;一点点写&#xff0c;拆解LSTM计算过程和最后的总结 遗忘门的计算流程 拼接词向量&#xff0c;前面来的&#xff0c;现在输入的 然后进行计算&#xff1a;

浅谈移动端车牌识别技术的实现过程及应用场景

随着移动互联技术的飞速发展和智能设备的普及&#xff0c;Android、iOS平台上的车牌识别技术逐渐成熟并广泛应用于各个领域。该技术通过智能手机的摄像头捕捉车牌图像&#xff0c;利用先进的图像处理与机器学习算法&#xff0c;实现车牌号码的自动识别。相比传统的人工录入或固…

opencv中Core中的Norm函数解释

1. Norm的类型 NORM_L1&#xff1a; L1 范数&#xff08;曼哈顿范数&#xff09;。数组中所有元素绝对值之和。 NORM_L2&#xff1a; L2 范数&#xff08;欧几里得范数&#xff09;。数组中所有元素平方和的平方根。 NORM_INF&#xff1a;无穷范数&#xff08;最大绝对值范数&…

Nginx的7大调度算法详解

Nginx的7大调度算法详解 一、Sticky二、Round-Robin&#xff08;RR&#xff09;三、Weight四、Least_conn五、IP_hash六、Fair七、URL_hash总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; Nginx作为一款高性能的HTTP和反向代理服务器&a…