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

- ROM的存储介质是FLASH、RAM的存储介质是SRAM
- FLASH 闪存掉电不丢失、SRAM掉电丢失
- 闪存主要有程序存储器、系统存储器、选项字节三个部分。 
  - 其中程序存储器是空间最大,最主要的部分。所以也乘坐主存储器。起始地址为0x0800 0000 ,是用来存储编译后的代码 所以我们平时所说的FLASH容量,往往指的是FLASH中程序存储器的容量
- 系统存储器起始地址为0x 1FFF F000 ,用于存储BootLoader ,用于串口下载
- 选项字节其实地址为0x1FFF F800 ,用于存储一些独立的配置参数
 
- SRAM区域分为运行内存SRAM、外设寄存器、内核外设寄存器 
  - SRAM运行内存的起始地址是0x2000 0000 , 用于存储运行过程的临时变量
- 外设寄存器的起始地址是0x4000 0000 , 用于存储各个外设的配置参数
- 内核外设寄存器 的起始地址是0xE000 0000 , 用于存储内核各个外设的配置参数
 
3. STM32 FLASH 容量、内容介绍
-  FLASH在 STM32中根据不同的型号,容量也不同。 - 以stm32F10x系列为例
- 小容量产品:32页 ,每页1K
- 中容量产品:128页,每页1K
- 大容量产品:256页,每页2K
 
-  以中容量为例讲解:  这个图中,把FLASH分为了三个块: - 主存储器(程序存储器):用来存放编译后的程序
- 信息快: 又可以分为两个 
    - 启动程序代码 (系统存储器): 存放原厂写入的BootLoader,用于串口下载
- 用户选择字节(选项字节) :用于存放一些独立的参数
 
- 闪存存储器接口寄存器:这个的地址是40开头的 ,根据SRAM的地址分配可以看到。闪存存储器接口寄存器是一个外设,与GPIO、定时器、串口等是一个性质的东西。 闪存存储器接口可以理解为上述FLASH闪存的管理员。是用来控制闪存的擦除和编程的
 对于F103 C8T6主存储器有0-63页,共64页,也就是64K - 地址的规律:只要是000、400、800、C00 结尾的。就是页的起始地址。
 - 启动程序代码(系统存储器)占用了1K空间,地址为0x1FFF F000
- 用户选择字节(选项字节):只有16个字节的配置参数
 闪存存储器接口寄存器,每个寄存器占4个字节。 
4. STM32 FLASH 读写注意事项
-  通过闪存存储器接口(外设)我们可以对程序存储器和选项字节进行擦除和编程。 **但系统存储器是不可以修改的。**它是用来存放原厂写入的用于串口下载的BootLoader的。 
-  在选取FLASH存储区域时,一定不要覆盖原有的程序。不然就运行不了了。 
-  所有的FLASH闪存 的写入和擦除规定了: - 写入前必须擦除
- 擦除必须以最小单位进行(这里为页 1K)
- 擦除后数据位全变为1
- 数据只能1写0 ,不能0写1
- 擦除和写入需要等待忙
 
-  在写入数据时,如果指定地址没有被擦除,那么就不会执行编程。同时提出警告(例外是写入0000,这样不会出问题。) 
-  在写入数据时,如果指定地址为写保护状态,那么也不会执行编程。同时提出警告。 
-  在整片擦除时,信息块不受影响(系统存储器和选项字节) 
-  在编程过程中(BSY为1)时,任何读写闪存的操作都会使CPU暂停。直到此次读写结束。 这时读写内部闪存存储数据的一个弊端。在闪存忙的时候,代码执行会暂停。 会导致 在读写内部闪存的时候,中断响应不及时等对时间要求比较严格的。 
-  对选项字节来编程的时候:WRP0位都是反逻辑位:0为实施写保护,1为取消写保护(因为闪存擦除之后都是1,1 是默认的) 
-  善用STM32 ST-LINK Utility 调试工具 
5. STM32 FLASH 基本结构
以C8T6为例

6. STM32 FLASH 读写步骤
6.1 FLASH 解除或添加 读、写保护的方法
FLASH需要再写入之前解除写保护。这里的操作方式和独立看门狗一样。是通过键寄存器写入特定的键值来实现。可以防止误操作
- FPEC共有三个键值: 
  - RDPRT键 (解除读保护)= 0x000000A5
- KEY1 (解除写保护1)= 0x45670123
- KEY2 (解除写保护2)= 0xCDEF89AB
 
- 解锁: 
  - 复位后,FPEC被保护,不能写入FLASH_CR(默认是锁的)
- 在FLASH_KEYR先写入KEY1,再写入KEY2,解锁
- 错误的操作序列会在下次复位前锁死FPEC和FLASH_CR
 
- 加锁: 
  - 设置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语言中易变的数据,这是一个安全保障措施。
- 一能防止编译器优化,(连续对某个变量赋值,或者执行空循环 会在开启优化时被优化)
- 二能告诉编译器,这个变量是个易变的数据。每次读取都要到位,直接从内存中找,不要去缓存中读取。
6.3 FLASH 闪存 全 擦除 时 过程

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

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

STM32的闪存在写入之前会检查指定地址有没有擦除。如果没有擦除就写入, STM32则不执行写入操作(除非写入的数据全是0)
- 读取LOCK位,看看芯片是否被锁, 
  - 如果锁住,就执行解锁过程(在KEYR寄存器先写入KEY1,再写入KEY2)
- 如果解锁,就置控制寄存器的PG**(Programming** 程序编制)表示我们即将写入数据、
 
- 在指定的地址写入半字(16位):*((__IO uint16_t *)(0x08000000)) = 0x1234;(不需要置STRT了)
- 判断状态控制器BSY(Busy 忙)是否为1 , 如果为1 就继续判断。等待他忙完(BSY = 0)跳出循环
- 最后一步验证一般不管。
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 选项字节 的擦除和编程
选项字节本身也是闪存,所以在写入前也要擦除。流程和程序存储器类似,但细节有些出入
擦除 选项字节时 过程
- 解锁FLASH闪存 (手册中没写,但一定要打开 这个相当于 门口大锁)
- 检查FLASH_SR的BSY位,以确认没有其他正在进行的闪存操作 (事前等待)
- 解锁FLASH_CR的OPTWRE (Option Write Enable 选项写入使能)位 (相当于卧室小锁)
- 设置FLASH_CR的OPTER(Option Erase 选项清除)位为1 (即将擦除选项字节)
- 设置FLASH_CR的STRT(Start 开始)位为1 (触发芯片开始干活)
- 等待BSY位变为0 (等待忙完)
- 读出被擦除的选择字节并做验证
编程 选项字节时 过程
- 解锁FLASH闪存 (手册中没写,但一定要打开 这个相当于 门口大锁)
- 检查FLASH_SR的BSY位,以确认没有其他正在进行的闪存操作 (事前等待)
- 解锁FLASH_CR的OPTWRE (Option Write Enable 选项写入使能)位 (相当于卧室小锁)
- 设置FLASH_CR的OPTPG(Option Programming 选项编程)位为1 (即将开始编程写入)
- 写入要编程的半字(16位)到指定的地址
- 等待BSY位变为0 (等待忙完)
- 读出被擦除的选择字节并做验证
9 . STM32 FLASH 器件的电子签名
电子签名存放在闪存存储器模块的系统存储区域(就是BootLoader哪里),包含的芯片识别信息在出厂时编写,不可更改,使用指针读指定地址下的存储器可获取电子签名
闪存容量寄存器:(显示闪存容量) 基地址:0x1FFF F7E0 大小:16位
产品唯一身份标识寄存器:( 可以用来作为序列号、防被盗、激活秘钥) 基地址: 0x1FFF F7E8 大小:96位 可以以字节、半字、字的方式读取
10. STM32 ST-LINK Utility调试工具的使用
- 连接 
  - 插入好ST link之后点击连接(不能在stm32休眠模式下连接)
 
- 退出 
  - 在使用完ST-LINK Utility调试工具后要及时断开,不然KEIl下载不了程序
 
- 查看程序数据 
  - 连接后,下面窗口中就是闪存中的数据了
- 可以指定地址 查看数据、指定查看范围、指定以什么类型去看(字节、半字、字、)
 

-  查看选项字节配置 - 进入
   - 功能介绍
  
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);
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)
    {
    }
}









