STM32 Flash 实战全解:存储结构、编程操作与安全机制深度剖析

发布于:2025-06-11 ⋅ 阅读:(21) ⋅ 点赞:(0)

FLASH

FLASH简介

STM32 Flash 基础概念

1. Flash 存储结构
  • 组成部分
    • 程序存储器(主存储器):存储程序代码,起始地址0x08000000,容量根据芯片型号不同(如 C8T6 为 64KB)。
    • 系统存储器:存储原厂 Bootloader,用于串口下载程序,起始地址0x1FFFF000,容量 2KB。
    • 选项字节(Option Bytes):存储配置参数(如读写保护、看门狗设置),起始地址0x1FFFF800,容量 16 字节。
  • 特点:非易失性存储器(掉电不丢失),写入前需擦除,擦除最小单位为页(Page)。
2. 核心术语
  • 页(Page)

    • 小容量产品(≤32KB):32 页,每页 1KB。
    • 中容量产品(64-128KB):128 页,每页 1KB(如 C8T6 为 64 页)。
    • 大容量产品(≥256KB):256 页,每页 2KB。
  • 字(Word):32 位数据;半字(Halfword):16 位数据;字节(Byte):8 位数据。

  • ICP(在线编程):通过外部工具直接对嵌入式系统设备进行存储器进行编程,无需拆除芯片,程序内容直接全部更新。

    • 硬件接口
      • ST-Link/J-Link:通过 SWD/JTAG 协议连接芯片,适用于调试和批量下载。
      • 串口(USART):通过芯片内置的 Bootloader(系统存储器)接收数据,需配置启动引脚(如 BOOT0=1)触发串口下载模式。
  • IAP(在应用编程):IAP 指程序在运行过程中自行更新存储器内容,允许设备在不依赖外部工具的情况下实现 “自我升级”,是实现 OTA(空中下载技术) 的基础。

    • 核心组件:Bootloader
      • 作用
        • 作为独立于主程序的 “引导程序”,负责接收新程序数据并写入 Flash。
        • 主程序(App)运行时,Bootloader 处于静默状态;需要升级时,主程序跳转至 Bootloader 执行更新逻辑。
      • 存储位置
        • 通常存放在 Flash 非易失性区域(如主存储区的起始段或独立分区),确保更新过程中不被覆盖。
    • 工作流程
      1. 烧录 Bootloader
        • 通过 ICP 提前烧录 Bootloader 到芯片(如地址 0x08000000)。
      2. 主程序运行
        • 主程序存放在 Bootloader 之后的地址(如 0x08005000),正常运行时通过函数跳转执行。
      3. 触发升级
        • 主程序检测到升级指令(如串口接收到特定命令、蓝牙信号),跳转至 Bootloader。
      4. 接收新程序
        • Bootloader 通过任意通信接口(串口、USB、蓝牙、Wi-Fi 等)接收升级包数据。
      5. 擦写 Flash
        • 擦除主程序存储区,将新程序写入指定地址。
      6. 重启运行新程序
        • 更新完成后,跳转至新程序起始地址,或复位芯片加载新程序。
  • 类型 起始地址 存储器 用途
    ROM 0x0800 0000 程序存储器Flash 存储C语言编译后的程序代码
    系统存储器 0x1FFF F000 存储BootLoader,用于串口下载 存储BootLoader,用于串口下载
    0x1FFF F800 0x1FFF F800 存储一些独立于程序代码的配置参数 存储一些独立于程序代码的配置参数
    RAM 0x2000 0000 运行内存SRAM 存储运行过程中的临时变量
    外设寄存器 0x4000 0000 存储各个外设的配置参数 存储各个外设的配置参数
    内核外设寄存器 0xE000 0000 存储内核各个外设的配置参数 存储内核各个外设的配置参数

闪存模块组织

1. 主存储器(程序存储器)
  • 结构
    • 128 页,每页 1K 字节(地址范围 0x080000000x0801FFFF,每页间隔 0x400 字节,如表格所示)。
    • 用途:存储程序代码,剩余空间可用于 用户数据存储(掉电不丢失)或 IAP 程序更新(动态升级)。
      • 当代码比较少时,空余部分较多,可以节省空间的利用等。
2. 信息块
  • 系统存储器(启动程序代码)
    • 地址:0x1FFF F000 - 0x1FFF F7FF,长度 2K 字节,存储原厂 Bootloader(支持 ICP 下载,如串口更新程序)。
  • 选项字节(用户选择字节)
    • 地址:0x1FFF F800 - 0x1FFF F80F,长度 16 字节,存储 配置参数(读写保护、看门狗设置等)。
    • 操作:需解锁 OPT KR 寄存器(写入反码验证),支持擦除(OPTER 位)和编程(OPT PG 位),对应会议中 “选项字节擦除与编程” 流程。
3. 闪存存储器接口寄存器
  • 起始地址0x40022000,每个寄存器4 字节,核心功能:
    • KR(密钥寄存器,FLASH_KEYR):解锁 / 加锁 Flash(写入 K1/K2 密钥)。
    • SR(状态寄存器,FLASH_SR):监控 BUSY位(擦写进度)、EOP位(操作完成)。
    • CR(控制寄存器,FLASH_CR):控制擦除(PER 页擦除、MER 全擦除)、写入(PG 半字写入)及加锁(LOCK位)。
    • AR(地址寄存器,FLASH_AR):指定擦除页地址(页擦除时使用)。
    • OBR(选项字节寄存器,FLASH_OBR):存储选项字节配置(如 RDP 读保护、WRP 写保护)。
    • WRPR(写保护寄存器,FLASH_WRPR):配置写保护页(反逻辑,0 表示保护生效)。
image-20250530153855238

FLASH基本结构

FLASH解锁

1. FPEC 密钥与安全机制
  • 密钥体系
    • 主存储区解锁密钥KEY1=0x45670123KEY2=0xCDF89AB,用于解锁FLASH_CR寄存器,允许擦除 / 写入操作。
    • 选项字节读保护密钥RDPRT键=0x000000A5,用于配置RDP位(解除读保护时使用,反逻辑验证)。
  • 复位保护:复位后 FPEC 默认锁定,禁止操作FLASH_CR,需通过合法密钥序列解锁,防止误操作。
2. 解锁流程(主存储区操作)
  • 步骤
    1. 写入 KEY1:向FLASH_KEYR寄存器写入0x45670123
    2. 写入 KEY2:接着写入0xCDF89AB,完成解锁(顺序严格,硬件验证)。
    3. 操作权限:解锁后可设置FLASH_CRPER(页擦除)、MER(全擦除)、PG(写入)等位,执行 Flash 操作。
  • 错误处理:若密钥顺序错误或非法值,FPEC 和FLASH_CR锁死,需复位芯片重新解锁,确保操作合法性。
3. 加锁流程
  • 操作:设置FLASH_CRLOCK位1表示加锁),禁止后续对FLASH_CR的写入,保护 FPEC。
  • 场景:擦除 / 写入完成后立即加锁,防止程序异常(如中断、跑飞)导致的 Flash 误操作,提升系统稳定性。

使用指针访问存储器

直接操作指针来对存储器进行访问,为了防止编译器的优化,使用volatile 关键字 ,来防止编译器的优化作用。

volatile 关键字详解

一、基本概念与作用

volatile是 C/C++ 等语言的类型修饰符,核心功能是禁止编译器对变量的优化,确保每次读写都直接操作内存(而非寄存器缓存)。其作用包括:

  1. 防止编译器优化:避免变量被缓存到寄存器,确保每次从内存读取最新值(如硬件寄存器、多线程共享变量)。
  2. 保证内存可见性:多线程或硬件场景下,变量修改后其他线程 / 硬件操作能立即感知(如 STM32 Flash 指针操作中__IO宏定义为volatile,保证实时读写)。
  3. 禁止指令重排序:通过内存屏障确保代码执行顺序与预期一致(如多线程同步逻辑)。
二、工作原理
  • 编译器行为:对volatile变量,编译器生成代码时不缓存其值,每次访问都显式读写内存。
  • 硬件交互:在嵌入式开发中(如 STM32),直接映射的硬件地址(如 Flash、寄存器)需用volatile确保操作真实作用于硬件(如会议中*(__IO uint16_t*)addr__IOvolatile,保证 Flash 读写的实时性)。
三、典型使用场景
  1. 硬件寄存器访问(嵌入式开发)

    • 读写 STM32 的 Flash、GPIO、UART 等寄存器时,volatile确保每次操作直接访问硬件,避免编译器优化(如会议中 Flash 指针操作的代码示例)。

    • 示例:

      #define GPIOA_ODR (*(volatile uint32_t*)0x4001080C) // 直接访问GPIO输出寄存器
      GPIOA_ODR = 0x00000001; // 写操作,编译器不优化,确保硬件输出
      
  2. 多线程共享变量(并发编程)

    • 线程间的标志位(如停止标志stopFlag),volatile保证修改后其他线程立即读取最新值(避免线程缓存不一致)。
    • 注意:volatile不保证原子性(如cnt++需结合锁或原子类,但嵌入式场景需注意硬件中断与主程序的变量交互)。
  3. 中断 / 异步事件处理

    • 中断服务程序(ISR)中修改的变量(如interruptFlag),主程序用volatile读取,确保不会因编译器优化而 “忽略” 中断(如 STM32 的 EXTI 中断标志处理)。
  4. 防止空循环优化

    • 空循环(如延迟函数)添加volatile后,编译器不会优化掉循环(如for (volatile int i=0; i<1e6; i++);用于简单延迟)。
四、与会议内容的关联(STM32 Flash 操作)
  • Flash 指针操作:会议中代码使用__IO(即volatile)修饰指针,确保读写 Flash 时直接操作内存,避免编译器缓存优化(如*(__IO uint16_t*)addr,保证每次读写 Flash 硬件,而非寄存器缓存,这是实现掉电数据存储和 IAP 升级的基础)。
  • 硬件寄存器访问volatile保证对FLASH_KEYRFLASH_CR等寄存器的操作实时生效(解锁 / 加锁、擦除 / 写入流程中的寄存器读写,必须通过volatile确保硬件级响应,避免编译器优化导致的操作失效)。
五、代码示例解析(对应会议代码)
// 读取Flash数据(volatile确保从内存读取,无缓存)
uint16_t data = *(__IO uint16_t*)(0x08000000); // __IO是volatile宏,直接访问Flash地址

// 写入Flash(需先解锁,此处省略解锁代码,volatile保证写操作实时)
*(__IO uint16_t*)(0x08000000) = 0x1234; // 直接写Flash内存,编译器不优化,确保硬件写入
  • __IO:定义为volatile,确保指针访问的内存操作不被优化,与 STM32 硬件手册中的寄存器访问要求一致(会议中 “加 volatile 防止编译器优化” 的具体实现)。
六、总结

volatile是嵌入式开发(如 STM32)中处理硬件交互和内存可见性的关键关键字。在会议涉及的 Flash 操作中,它确保了指针读写的实时性(无编译器优化),是实现掉电数据存储、程序升级(IAP/ICP)的基础。其核心价值在于强制内存访问,避免编译器对硬件相关代码的优化,确保系统与硬件行为一致。合理使用volatile可提升嵌入式系统的稳定性,尤其在直接内存映射(如 Flash、寄存器)场景中不可或缺。

关键要点速记

  • 作用:禁止编译器优化,保证内存可见性,适用于硬件交互、多线程标志。
  • STM32 场景:Flash 指针操作(__IO宏)、寄存器访问(如FLASH_CR)均依赖volatile确保实时性。
  • 局限:不保证原子性,多线程复杂操作需结合锁 / 原子类,嵌入式中主要用于硬件相关代码。

程序存储器编程

流程解析
  • 解锁检查
    • 读取FLASH_CRLOCK位,若为1(加锁状态),执行解锁序列(写入KEY1KEY2FLASH_KEYR),解除 FPEC 保护( “解锁与加锁” 机制的具体实现)。
    • LOCK位0(已解锁),直接进入写入步骤,确保操作合法性。
  • 写入配置
    • FLASH_CRPG位1,使能半字(16 位)写入模式( “写入操作需设置 PG 位” 的硬件要求)。
  • 数据写入
    • 通过指针(如*(__IO uint16_t*)addr)向指定地址写入数据,利用volatile__IO宏)确保实时访问硬件(指针操作的volatile应用,防止编译器优化)。
  • 忙等待
    • 轮询FLASH_SRBSY位,直至0(操作完成),保证 CPU 在擦写期间不执行其他任务( “等待 busy 位清零” 的同步机制,确保操作原子性)。
  • 数据校验
    • 写入后读取地址数据,验证正确性,防止硬件故障或地址错误( 嵌入式开发的常规数据可靠性检查)。

程序存储器页擦除

流程解析
  • 解锁检查
    • 读取FLASH_CRLOCK位,若为1(加锁),执行解锁序列(写入KEY1KEY2FLASH_KEYR),解除 FPEC 保护(会议中 “解锁与加锁” 机制的硬件实现,确保操作合法性)。
    • LOCK位0(已解锁),直接进入擦除步骤。
  • 擦除配置
    • FLASH_CRPER位1(选择页擦除模式,区别于全擦除MER位, “页擦除” 与 “全擦除” 的硬件配置差异)。
    • FLASH_AR中指定擦除页地址(如中容量产品每页 1K,地址间隔0x400, “内存模块组织” 的页地址规划)。
    • FLASH_CRSTRT位1,触发擦除(硬件级启动擦除流程)。
  • 忙等待
    • 轮询FLASH_SRBSY位,直至0(擦除完成),确保 CPU 同步( “等待 busy 位清零” 的同步机制,避免硬件冲突,保证操作原子性)。
  • 数据验证
    • 擦除后读取页数据,验证是否为全0xFFFF(Flash 擦除后默认值),确保擦除成功(嵌入式开发常规可靠性检查,防止硬件故障)。

程序存储器全擦除

流程解析
  • 解锁检查
    • 读取FLASH_CRLOCK位,若为1(加锁),执行解锁序列(写入KEY1KEY2FLASH_KEYR),解除 FPEC 保护(与会议中 “解锁与加锁” 机制一致,确保操作合法性)。
    • LOCK位0(已解锁),直接进入全擦除步骤。
  • 擦除配置
    • FLASH_CRMER位1(选择全擦除模式,区别于页擦除PER位,会议中 “全擦除” 与 “页擦除” 的硬件配置差异)。
    • FLASH_CRSTRT位1,触发全擦除(擦除主存储区所有页,信息块和选项字节保留,符合会议 “内存模块组织” 的存储区划分)。
  • 忙等待
    • 轮询FLASH_SRBSY位,直至0(擦除完成),确保 CPU 同步(会议中 “等待 busy 位清零” 的同步机制,保证操作原子性)。
  • 数据验证
    • 擦除后读取所有页数据,验证是否为全0xFFFF(Flash 擦除后默认值),确保全擦除成功(常规可靠性检查,防止硬件故障)。

选项字节

  • 地址与反码设计
    选项字节位于0x1FFF F800起,共 16 字节,含RDP(读保护)、USER(硬件配置)、Data0/1(用户自定义)、WRP0-3(写保护)及其反码(n前缀字段),硬件验证写入合法性(反逻辑确保安全)。

  • 核心字段

    • RDP:写入RDPRT键(0x000000A5)解除读保护(开发时),默认值开启保护(量产防盗)。

      • 为什么读取地址数据的函数不需要 “开启读保护”?

        读保护的核心作用是限制外部工具(如 ST-Link)对 FLASH 的读取,防止固件被逆向破解。它的配置逻辑和你提供的读取函数的功能定位完全不同:

        • 读保护的实现机制

        读保护是通过 **选项字节(Option Bytes)**设置的(属于芯片级安全配置),而非通过应用层代码实现。具体来说:

        读保护等级(如 Level 1)会禁止外部调试接口(SWD/JTAG)读取 FLASH 内容;

        • 内部代码对 FLASH 的正常读取(如执行程序、读取用户数据)不受影响(这是芯片设计的基本要求)。
    • USER:配置看门狗、低功耗模式复位,影响系统启动与运行(硬件级功能定制)。

    • Data0/1:用户自定义存储(如设备参数、序列号),替代外挂存储,掉电不丢失。

    • WRP0-3:每 1 位保护 4 页(中容量),0生效(反逻辑),防止指定页擦写(数据安全防护)。

选项字节编程

流程解析
  • 忙等待检查(BSY 位):读取FLASH_SRBSY位,确保无正在进行的擦写操作,避免硬件冲突,保证操作原子性(对应会议中 “操作前同步检查” 的安全机制)。
  • 解锁选项字节写保护(OPTWARE位:写入RDPRT键(0x000000A5)解锁FLASH_CROPTWARE位,允许选项字节编程(会议中 “选项字节小锁解锁” 的硬件步骤,独立于主存储区解锁,实现分层安全)。
  • 配置编程模式(OPTPG位:置FLASH_CROPTPG位1,使能选项字节半字写入(与主存储区PG位类似,确保数据格式正确,会议 “半字写入” 的硬件配置)。
  • 数据写入:通过指针向选项字节地址(如0x1FFF F800)写入半字数据(如RDPWRP等配置,依赖volatile确保实时访问,会议中指针操作的__IO宏应用)。
  • 忙等待与校验:等待BSY位清零,读取地址验证数据,确保写入正确(会议 “数据可靠性检查”,防止硬件故障)。

读取Flash数据有关代码

//MyFLASH.c

#include "stm32f10x.h"                  // Device header

//读取数据不用开启读保护,读保护是应用于外部的设备对flash地址内容的读取
//读取指定地址的字
uint32_t MyFLASH_ReadWord(uint32_t Address)
{
	return *((__IO uint32_t*) Address);
}

//FLASH读取半字
uint16_t MyFLASH_ReadHalfWord(uint32_t Address)
{
	return *((__IO uint16_t *)(Address));
}

// 读取字节
uint8_t MyFLASH_ReadByte(uint32_t Address)
{
	return *((__IO uint8_t *) Address);
}

void MyFLASH_EraseAllPage(void)
{
	//先开锁
	FLASH_Unlock();
	FLASH_EraseAllPages();
	//上锁
	FLASH_Lock();
}

void MyFlash_ErasePage(uint32_t PageAddress)
{
	//先开锁
	FLASH_Unlock();
	FLASH_ErasePage(PageAddress);
	//上锁
	FLASH_Lock();
}

void MyFlash_ProgramWord(uint32_t Address, uint32_t Data)
{
	//先开锁
	FLASH_Unlock();
	FLASH_ProgramWord(Address, Data);
	//上锁
	FLASH_Lock();
}

void MyFlash_ProgramHalfWord(uint32_t Address, uint16_t Data)
{
	//先开锁
	FLASH_Unlock();
	FLASH_ProgramHalfWord(Address, Data);
	//上锁
	FLASH_Lock();
}


//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数组



//模块实现的功能 就是使用一个数组来存储 最后一页的 flash 数据,因为 进行对flash数据进行操作是不方便的,
//使用一个数组来作为flash模块的分身,想要对flash中数据进行变化时,直接对数组中数据进行变化,然后再同步到flash
//这样数据也就有了数据掉电不丢失的性质,上电之后再将存储到flash中的数据读到数组中。


//初始化 判断如果flash中已经存储了数据,将数据读出,否则将数据都初始化为0
void Store_Init(void)
{
	//判断标志位 将要存储的第一个半字 作为标志位,也就是用来判断是否 flash中已经存储过数据
	if(MyFLASH_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5)
	{
		//擦除页
		MyFlash_ErasePage(STORE_START_ADDRESS);
		//第一个半字写入 0xA5A5 也就是置标志位
		MyFlash_ProgramHalfWord(STORE_START_ADDRESS, 0xA5A5);
		//将整页的数据初始化为0, 不包括第一个半字
		for(uint16_t i = 1; i < STORE_COUNT; i++)
		{
			MyFlash_ProgramHalfWord(STORE_START_ADDRESS + i* 2, 0x0000);
		}
	}
	//上电之后将flash中存储的数据同步到数组中
	for(uint16_t i = 0; i < STORE_COUNT; i++)
	{
		//标志位也存到数组中
		Store_Data[i] = MyFLASH_ReadHalfWord(STORE_START_ADDRESS + i*2);
	}
}

//每次数组中数据变化一次 就将数组中数据存储一次
void Store_Save(void)
{
	//将数据写入到flash中时,要先进行页擦除
	MyFlash_ErasePage(STORE_START_ADDRESS);
	for(uint16_t i = 0; i < STORE_COUNT; i++)
	{
		MyFlash_ProgramHalfWord(STORE_START_ADDRESS + i*2, Store_Data[i]);
	}
}

void Store_Clear(void)
{
	for(uint16_t i = 1; i < STORE_COUNT; i++)
	{
		Store_Data[i] = 0x0000;
	}
	//将数据同步到flash中
	Store_Save();
}
//main.c

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

uint8_t keynum;
int main(void)
{
	OLED_Init();
	Key_Init();
	Store_Init();
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Flag:");
	OLED_ShowString(2, 1, "Data:");
	

	while(1)
   {
	   keynum = Key_GetNum();
	   if(keynum == 1)
	   {
		   Store_Data[1] ++;
		   Store_Data[2] +=2;
		   Store_Data[3] += 3;
		   Store_Data[4] += 4;
		   //写完数据要同步数据
		   Store_Save();
	   }
	   if(keynum == 2)
	   {
		   Store_Clear();
	   }
	   
	   OLED_ShowHexNum(1, 6, Store_Data[0], 4);
	   OLED_ShowHexNum(3, 1, Store_Data[1], 4);
	   OLED_ShowHexNum(3, 6, Store_Data[2], 4);
	   OLED_ShowHexNum(4, 1, Store_Data[3], 4);
	   OLED_ShowHexNum(4, 6, Store_Data[4], 4);
   }
}

选项字节擦除

流程解析

忙等待检查(BSY 位):读取FLASH_SRBSY位,确保无正在进行的闪存操作(擦除 / 写入),避免硬件冲突,保证操作原子性(与会议中 “操作前同步检查” 的安全机制一致,如全擦除、页擦除前的 BSY 检查)。

解锁选项字节写保护(OPTWARE位:写入RDPRT键(0x000000A5)解锁FLASH_CROPTWARE位,允许选项字节擦除(会议中 “选项字节小锁解锁” 的硬件步骤,独立于主存储区解锁,实现分层安全防护)。

配置擦除模式(OPTER位:置FLASH_CROPTER位1,选择选项字节擦除模式(区别于主存储区擦除的PER/MER位,会议中 “选项字节擦除的硬件配置”)。

触发擦除(STRT位:置FLASH_CRSTRT位1,启动选项字节擦除(硬件级操作,确保擦除指令生效)。

忙等待与校验:等待BSY位清零,读取选项字节地址验证是否为全0xFFFF(擦除后默认值),确保擦除成功(会议 “数据可靠性检查”,防止硬件故障)。

器件电子签名

1. 存储与特性

系统存储区:电子签名位于闪存系统存储区(出厂写入,不可更改),通过指针(volatile修饰,确保实时读取)访问,如*(__IO uint32_t*)0x1FFF F7E8获取 96 位唯一 ID(UID),0x1FFF F7E0获取 16 位 Flash 容量(FSize)。

2. 核心寄存器

闪存容量(FSize):地址0x1FFF F7E0,16 位,标识芯片 Flash 大小(如 64KB 对应0x0040),用于硬件适配(如动态内存分配)。

唯一身份标识(UID):地址0x1FFF F7E8,96 位,芯片唯一 ID,支撑设备认证、软件授权(如物联网设备 ID、加密序列号)。

3. 应用场景

安全认证:UID 作为设备唯一标识,实现物联网设备身份验证(如 MQTT 协议中的设备 ID),防止盗版(结合加密算法生成授权码)。

硬件识别:读取 FSize 自动适配程序(如代码中根据容量调整存储策略),提升兼容性。

4. 与会议关联

代码实现:会议中 “读取芯片 ID” 的代码示例(指针操作)直接访问上述寄存器,体现内存映射的硬件级读取(简洁高效,无需外设)。

安全机制:UID 的唯一性增强系统安全(如 IAP 升级时的身份校验,防止恶意代码注入)。

5. 总结

器件电子签名通过只读寄存器提供硬件级唯一标识(UID)和容量信息(FSize),支持设备认证、安全授权等功能。结合会议中的指针操作(volatile确保实时性),实现高效、安全的硬件识别,为嵌入式系统的智能化(物联网、安全防护)奠定基础,提升系统安全性与可维护性。

查看芯片id有关代码

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

//想要读取芯片的id直接在指定地址读就可以了

int main(void)
{
	OLED_Init();
	Key_Init();
	Store_Init();
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "F-SIZE:");
	OLED_ShowHexNum(1, 8, *((__IO uint16_t*) 0x1FFFF7E0), 4);
	OLED_ShowString(2, 1, "UID:");
	
	//读参考手册查看id有关信息所在地址
	OLED_ShowHexNum(2, 5, *((__IO uint16_t*) 0x1FFFF7E8), 4);
	OLED_ShowHexNum(2, 10, *((__IO uint16_t*) (0x1FFFF7E8 + 0x02)), 4);
	OLED_ShowHexNum(3, 1, *((__IO uint32_t*) (0x1FFFF7E8 + 0x04)), 8);
	OLED_ShowHexNum(4, 1, *((__IO uint32_t*) (0x1FFFF7E8 + 0x08)), 8);
	while(1)
   {
	   
   }
}