【正点原子STM32连载】 第四十五章 FLASH模拟EEPROM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

第四十五章 FLASH模拟EEPROM实验

STM32本身没有自带EEPROM,但是STM32具有IAP(在应用编程)功能,所以我们可以把它的FLASH当成EEPROM来使用。本章,我们将利用STM32内部的FLASH来实现第三十六章实验类似的效果,不过这次我们是将数据直接存放在STM32内部,而不是存放在NOR FLASH。
本章分为如下几个小节:
45.1 STM32 FLASH简介
45.2 硬件设计
45.3 软件设计
45.4 下载验证

45.1 STM32 FLASH简介

不同型号的STM32,其FLASH容量也有所不同,最小的只有16K字节,最大的则达到了1024K字节。战舰开发板选择的是STM32F103ZET6,其FLASH容量为512K字节,属于大容量产品(另外还有中容量和小容量产品),大容量产品的闪存模块组织如表45.1.1所示:
【正点原子STM32连载】 第四十五章 FLASH模拟EEPROM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第1张图片

表45.1.1 大容量产品闪存模块组织表
STM32的闪存模块由主存储器、信息块和闪存存储器接口寄存器等3部分组成。
主存储器,该部分用来存放代码和数据常数(如const类型的数据)。对于大容量产品,其被划分为256页,每一页2K字节(注意:小容量和中容量产品每页只有1K字节)。从上表可以看出主存储器的起始地址就是0x08000000,B0、B1都接GND的时候,就是从0x08000000开始运行代码的。
信息块,该部分分为2个小部分,其中启动程序代码,用来存储ST自带的启动程序,用来串口下载代码,当B0接3V3,B1接GND的时候,运行的就是这部分代码。用户选中字节,则一般用于配置写保护、读保护等功能,本章不作介绍了。
闪存存储器接口寄存器,该部分用于控制闪存读写等,是整个闪存模块的控制结构。
对主存储器和信息块的写入由内嵌的闪存编程/擦除控制器(FPEC)管理;编程与擦除的高电压由内部产生。
在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行。既在进行写或擦除操作时,不能进行代码或数据的读取操作。
45.1.1 闪存的读取
内置闪存模块可以在通用地址空间直接寻址,任何32位数据的读操作都能访问闪存模块的内容并得到相应的数据。读接口在闪存端包含一个读控制器,还包含一个AHB接口与CPU衔接。这个接口的主要工作是产生读内存的控制信号并预取CPU要求的指令块,预取指令块仅用于在I-Code总线上的取指操作,数据常量是通过D-Code总线访问的。这两条总线的访问目标是相同的闪存模块,访问D-Code将比预取指令优先级高。
这里要特别留意一个闪存等待时间,因为CPU运行速度比FLASH快得多,STM32F103的FLASH最快访问速度≤24Mhz,如果CPU频率超过这个速度,那么必须加入等待时间,比如我们一般使用72Mhz的主频,那么FLASH等待周期就必须设置为2,该设置通过FLASH_ACR寄存器设置。
例如,我们要从地址addr,读取一个半字(半字为16位,字为32位),可以通过如下的语句读取:
data = (vu16)addr;
将addr强制转换为vu16指针,然后取该指针所指向的地址的值,即得到了addr地址的值。类似的,将上面的vu16改为vu8,即可读取指定地址的一个字节。相对FLASH读取来说,STM32 FLASH的写就复杂一点了。下面我们介绍STM32闪存的编程和擦除。
45.1.2 闪存的编程和擦除
STM32的闪存编程是由FPEC(闪存编程和擦除控制器)模块处理的,这个模块包含7个32位寄存器,它们分别是:
FPEC键寄存器(FLASH_KEYR)
选择字节键寄存器(FLASH_OPTKEYR)
闪存控制寄存器(FLASH_CR)
闪存状态寄存器(FLASH_SR)
闪存地址寄存器(FLASH_AR)
选择字节寄存器(FLASH_WRPR)
其中FPEC键寄存器总共有3个键值:
RDPRT键 = 0X0000 00A5
KEY1 = 0X4567 0123
KEY2 = 0XCDEF 89AB
STM32复位后,FPEC模块是被保护的,不能写入FLASH_CR寄存器;通过写入特定的序列到FLASH_KEYR寄存器可以打开FPEC模块(即写入KEY1和KEY2),只有在写保护被解除后,我们才能操作相关寄存器。
STM32闪存的编程每次必须写入16位(不能单纯的写入8位数据),当FLASH_CR寄存器的PG位为‘1’时,在一个闪存地址写入一个半字将启动一次编程;写入任何非半字的数据,FPEC都会产生总线错误。在编程过程中(BSY位为’1’),任何读写内存的操作都会使CPU暂停,直到此次闪存编程结束。
同样,STM32的FLASH在编程的时候,也必须要求其写入地址的FLASH是被擦除了的(其值必须是0xFFFF),否则无法写入,在FLASH_SR寄存器的PGERR位将得到一个警告。
STM32的FLASH编程过程如图45.1.2.1所示:
【正点原子STM32连载】 第四十五章 FLASH模拟EEPROM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第2张图片

图45.1.2.1 STM32闪存编程过程
从上图可以得到闪存的编程顺序如下:
1)检查FLASH_CR的LOCK是否解锁,如果没有则先解锁
2)检查FLASH_SR寄存器的BSY位,以确认没有其他正在进行的编程操作
3)设置FLASH_CR寄存器的PG位为‘1’
4)在指定的地址写入要编程的半字
5)等待BSY位变为‘0’
6)读出写入地址并验证数据
前面提到,我们在STM32的FLASH编程的时候,要先判断缩写地址是否被擦出了,所以,我们有必要再介绍一下STM32的闪存擦除,STM32的闪存擦除分为两种:页擦除和整片擦除。页擦除过程如图45.1.2.2所示:
【正点原子STM32连载】 第四十五章 FLASH模拟EEPROM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第3张图片

图45.1.2.2 STM32闪存页擦除过程
从上图可以看出,STM32的页擦除顺序为:
1)检查FLASH_CR和LOCK是否解锁,如果没有则先解锁
2)检查FLASH_SR寄存器的BSY位,以确认没有其他正在进行的闪存操作
3)设置FLASH_CR寄存器的PER位为‘1’
4)用FLASH_AR寄存器选择要擦除的页
5)设置FLASH_CR寄存器的STRT位为‘1’
6)等待BSY位变为‘0’
7)读出被擦除的页并做验证
本章我们只用到了STM32页擦除功能,整片擦除功能我们在这里就不介绍了。
45.1.3 FLASH寄存器
通过上面的讲解,我们基本对STM32闪存的读写执行步骤有所了解。接下来,我们介绍本实验需要用到的一些FLASH寄存器。
 FPEC键寄存器(FLASH_KEYR)
FPEC键寄存器描述如图45.1.3.2所示:
【正点原子STM32连载】 第四十五章 FLASH模拟EEPROM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第4张图片

图45.1.3.2 FLASH_KEYR寄存器
该寄存器主要用来解锁FPEC,必须在该寄存器写入特定的序列(KEY1和KEY2)解锁后,才能对FLASH_CR寄存器进行写操作。
 FLASH控制寄存器(FLASH_CR)
FLASH控制寄存器描述如图45.1.3.3所示:
【正点原子STM32连载】 第四十五章 FLASH模拟EEPROM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第5张图片

图45.1.3.3 FLASH_CR寄存器
该寄存器我们本章只用到了它的LOCK、STRT、PER和PG等4个位。
LOCK位,该位用于指示FLASH_CR寄存器是否被锁住,该位在检测到正确的解锁序列后,硬件将其清零。在一次不成功的解锁操作后,在下次系统复位之前,该位将不再改变。
STRT位,该位用于开始一次擦除操作。在该位写入1,将执行一次擦除操作。
PER位,该位用于选择页擦除操作,在页擦除的时候,需要将该位置1。
PG位,该位用于选择编程操作,在往FLASH写数据的时候,该位需要置1。
其他位,我们就不在这里介绍了,请大家参考《STM32F10xxx闪存编程参考手册》。
 闪存状态寄存器(FLASH_SR)
闪存状态寄存器描述如图45.1.3.4所示:
【正点原子STM32连载】 第四十五章 FLASH模拟EEPROM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第6张图片

图45.1.3.4 FLASH_SR寄存器
该寄存器主要用来指示当前FPEC的操作编程状态。由于寄存器中描述比较详细,这里就不重复了。
 闪存地址寄存器(FLASH_AR)
闪存地址寄存器描述如图45.1.3.5所示:
【正点原子STM32连载】 第四十五章 FLASH模拟EEPROM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第7张图片

图45.1.3.5 FLASH_AR寄存器
该寄存器在本章,我们主要用来设置要擦除的页。
关于STM32 FLASH的介绍,我们就介绍到这里。更详细的介绍,可以参考《STM32F10xxx闪存编程参考手册》。
45.2 硬件设计

  1. 例程功能
    按键KEY1控制写入FLASH的操作,按键KEY0控制读出操作,并在TFTLCD模块上显示相关信息,还可以借助USMART进行读取或者写入操作。LED0闪烁用于提示程序正在运行。
  2. 硬件资源
    1)LED灯
    LED0 – PB5
    2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)
    3)正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
    4)独立按键
    KEY0 – PE4 KEY1 – PE3
    45.3 程序设计
    45.3.1 FLASH的HAL库驱动
    FLASH在HAL库中的驱动代码在stm32f1xx_hal_flash.c和stm32f1xx_hal_flash_ex.c文件(及其头文件)中。
  3. HAL_FLASH_Unlock函数
    解锁闪存控制寄存器访问的函数,其声明如下:
    HAL_StatusTypeDef HAL_FLASH_Unlock(void);
    函数描述:
    用于解锁闪存控制寄存器的访问,在对FLASH进行写操作前必须先解锁,解锁操作也就是必须在FLASH_KEYR寄存器写入特定的序列(KEY1和KEY2)。
    函数形参:

    函数返回值:
    HAL_StatusTypeDef枚举类型的值。
  4. HAL_FLASH_Lock函数
    锁定闪存控制寄存器访问的函数,其声明如下:
    HAL_StatusTypeDef HAL_FLASH_Lock (void);
    函数描述:
    用于锁定闪存控制寄存器的访问。
    函数形参:

    函数返回值:
    HAL_StatusTypeDef枚举类型的值。
  5. HAL_FLASH_Program函数
    闪存写操作函数,其声明如下:
    HAL_StatusTypeDef HAL_FLASHEx_Program(uint32_t TypeProgram, uint32_t Address,
    uint64_t Data);
    函数描述:
    该函数用于FLASH的写入。
    函数形参:
    形参1是TypeProgram用来区分要写入的数据类型,取值可为字节、半字、字和双字,用户根据写入数据类型选择即可。
    形参2是Address用来设置要写入数据的FLASH地址。
    形参3是Data是要写入的数据类型。该参数默认64位,如果你要写入小于64位的数据,比如16位,程序会进行类型转换。
    函数返回值:
    HAL_StatusTypeDef枚举类型的值。
  6. HAL_FLASHEx_Erase函数
    闪存擦除函数,其声明如下:
    HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit,
    uint32_t *SectorError);
    函数描述:
    该函数用于大量擦除或擦除指定的闪存扇区。
    函数形参:
    形参1是FLASH_EraseInitTypeDef结构体类型指针变量。
typedef struct
{
  uint32_t TypeErase;  		/* 擦除类型(Page擦除 / BANK级别批量擦除) */
  uint32_t Banks;        	/* 擦除的Bank编号(批量擦除时才有效) */    
  uint32_t PageAddress; 	/* 擦除页面地址 */
  uint32_t NbPages;      	/* 擦除的页面数 */
} FLASH_EraseInitTypeDef;

成员变量TypeErase用来设置擦除类型,是page擦除还是BANK级别的批量擦除,取值为FLASH_TYPEERASE_PAGES或者FLASH_TYPEERASE_MASSERASE,这个比较好理解,如果一次擦除一个Bank下面的所有Page,那么需要选择FLASH_TYPEERASE_MASSERASE。成员变量Banks用来设置要擦除的Bank编号,这个只有设置为批量擦除的时候才有效。成员变量PageAddress用来设置要擦除页面的地址。成员变量NbPages用来设置要擦除的页面数。
形参2是uint32_t类型指针变量,存放错误码,0xFFFFFFFF值表示扇区已被正确擦除,其它值表示擦除过程中的错误扇区。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
5. FLASH_WaitForLastOperation函数
等待FLASH操作完成函数,其声明如下:
HAL_StatusTypeDef FLASH_WaitForLastOperation(uint32_t Timeout);
函数描述:
该函数用于等待FLASH操作完成。
函数形参:
形参1是FLASH操作超时时间。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
45.3.2 程序流程图
【正点原子STM32连载】 第四十五章 FLASH模拟EEPROM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第8张图片

图45.3.2.1 FLASH模拟EEPROM实验程序流程图
45.3.3 程序解析

  1. STM FLASH驱动代码
    这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。STM FLASH驱动源码包括两个文件:stmflash.c和stmflash.h。
    stmflash.h头文件做了一些比较重要的宏定义,定义如下:
/* FLASH起始地址 */
#define STM32_FLASH_BASE        0x08000000  		/* STM32 FLASH 起始地址 */
#define STM32_FLASH_SIZE        0x80000         	/* STM32 FLASH 总大小 */
/* STM32F103扇区大小 */
#if STM32_FLASH_SIZE < 256 * 1024
#define STM32_SECTOR_SIZE  1024		/* 容量小于256K的F103, 扇区大小为1K字节 */
#else
#define STM32_SECTOR_SIZE  2048		/* 容量大于等于256K的F103, 扇区大小为2K字节 */
#endif

STM32_FLASH_BASE和STM32_FLASH_SIZE分别是FLASH的起始地址和FLASH总大小,这两个宏定义随着芯片是固定的,我们战舰开发板的F103芯片FLASH是512K字节,所以STM32_FLASH_SIZE宏定义值为0x80000。
下面我们开始介绍stmflash.c的程序,下面先介绍一下stmflash写操作函数,源码如下:

/**
 * @brief     	在FLASH 指定位置, 写入指定长度的数据(自动擦除)
 * @note     	该函数往 STM32 内部 FLASH 指定位置写入指定长度的数据
 *              	该函数会先检测要写入的扇区是否是空(全0XFFFF)的?, 如果
 *             	不是, 则先擦除, 如果是, 则直接往扇区里面写入数据.
 *              	数据长度不足扇区时,自动被回擦除前的数据
 * @param     	waddr  	: 起始地址 (此地址必须为2的倍数!!,否则写入出错!)
 * @param     	pbuf   	: 数据指针
 * @param      	length 	: 要写入的 半字(16位)数
 * @retval    	无
 */
uint16_t g_flashbuf[STM32_SECTOR_SIZE / 2]; /* 最多是2K字节 */
void stmflash_write(uint32_t waddr, uint16_t *pbuf, uint16_t length)
{
    uint32_t secpos;    		/* 扇区地址 */
    uint16_t secoff;    		/* 扇区内偏移地址(16位字计算) */
    uint16_t secremain; 		/* 扇区内剩余地址(16位字计算) */
    uint16_t i;
    uint32_t offaddr;   		/* 去掉0X08000000后的地址 */
    FLASH_EraseInitTypeDef flash_eraseop;
uint32_t erase_addr;   	/* 擦除错误,这个值为发生错误的扇区地址 */

   if(waddr<STM32_FLASH_BASE||(waddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))
    {
        return; 				/* 非法地址 */
}

HAL_FLASH_Unlock();		/* FLASH解锁 */

    offaddr = waddr - STM32_FLASH_BASE;     		/* 实际偏移地址 */
    secpos = offaddr / STM32_SECTOR_SIZE;		/* 得到扇区编号 */
    secoff = (offaddr % STM32_SECTOR_SIZE) / 2;	/* 在扇区内的偏移(2B为基本单位) */
secremain = STM32_SECTOR_SIZE / 2 - secoff;	/* 扇区剩余空间大小 */

    if (length <= secremain)
    {
        secremain = length;	/* 不大于该扇区范围 */
}

    while (1)
    {
        stmflash_read(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,
 g_flashbuf, STM32_SECTOR_SIZE / 2);	/* 读出整个扇区的内容 */

        for (i = 0; i < secremain; i++)		/* 校验数据 */
        {
            if (g_flashbuf[secoff + i] != 0XFFFF)
            {
                break; 						/* 需要擦除 */
            }
        }

        if (i < secremain) 					/* 需要擦除 */
        { 
            flash_eraseop.TypeErase = FLASH_TYPEERASE_PAGES;	/* 选择页擦除 */
            flash_eraseop.NbPages = 1;						/* 要擦除的页数 */
            flash_eraseop.PageAddress = secpos * STM32_SECTOR_SIZE +  
STM32_FLASH_BASE;	/* 要擦除的起始地址 */
            HAL_FLASHEx_Erase(&flash_eraseop, &erase_addr);

            for (i = 0; i < secremain; i++)         		     /* 复制 */
            {
                g_flashbuf[i + secoff] = pbuf[i];
            }
            stmflash_write_nocheck(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE, 
   g_flashbuf, STM32_SECTOR_SIZE / 2); /* 写入整个扇区 */
        }
        else
        {	   /* 写已经擦除了的,直接写入扇区剩余区间 */
            stmflash_write_nocheck(waddr, pbuf, secremain); 
        }

        if (length == secremain)
        {
            break; 					/* 写入结束了 */
        }
        else       					/* 写入未结束 */
        {
            secpos++;             		/* 扇区地址增1 */
            secoff = 0;             	/* 偏移位置为0 */
            pbuf += secremain;      	/* 指针偏移 */
            waddr += secremain * 2; 	/* 写地址偏移(16位数据地址,需要*2) */
            length -= secremain;    	/* 字节(16位)数递减 */

            if (length > (STM32_SECTOR_SIZE / 2))
            {
                secremain = STM32_SECTOR_SIZE / 2;	/* 下一个扇区还是写不完 */
            }
            else
            {
                secremain = length; 	/* 下一个扇区可以写完了 */
            }
        }
}
    HAL_FLASH_Lock(); 				/* 上锁 */
}

该函数用于在STM32的指定地址写入指定长度的数据。函数的实现基本类似SPI章节的norflash_write函数,不过该函数对于写入地址是有要求,必须保证以下两点:
1、写入地址必须是用户代码区以外的地址。
2、写入地址必须是2的倍数。
第1点比较好理解,如果把用户代码给擦了,可想而知你运行的程序可能就被废了,从而很可能出现死机的情况。第2点则是STM32 FLASH的要求,每次必须写入16位,如果你写的地址不是2的倍数,那么写入的数据,可能就不是写在你要写的地址了。
另外,该函数的g_flashbuf数组,也是根据所用STM32的FLASH容量来确定的,战舰STM32开发板的FLASH是512K字节,所以STM_SECTOR_SIZE的值为2048,故该数组大小为2K字节。
stmflash_write函数实质是调用stmflash_write_nocheck函数进行实现,下面再来看一下stmflash_write函数代码,其代码如下:

/**
 * @brief     	不检查的写入
              	这个函数的假设已经把原来的扇区擦除过再写入
 * @param     	waddr  	: 起始地址 (此地址必须为2的倍数!!,否则写入出错!)
 * @param     	pbuf   	: 数据指针
 * @param   		length 	: 要写入的 半字(16位)数
 * @retval    	无
 */
void stmflash_write_nocheck(uint32_t waddr, uint16_t *pbuf, uint16_t length)
{
uint16_t i;

    for (i = 0; i < length; i++)
    {
        HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, waddr, pbuf[i]);
        waddr += 2; 		/* 指向下一个半字 */
    }
}

该函数的实现依靠flash的HAL库驱动HAL_FLASH_Program进行实现。由于前面已经对HAL_FLASH_Program进行说明,这里就不作展开说明了。
接下来,讲解一下STM FLASH读相关的函数,写函数也有调用到读函数,其代码如下:

/**
 * @brief      	从指定地址读取一个半字 (16位数据)
 * @param      	faddr   : 读取地址 (此地址必须为2的倍数!!)
 * @retval     	读取到的数据 (16位)
 */
uint16_t stmflash_read_halfword(uint32_t faddr)
{
    return *(volatile uint16_t *)faddr;
}

/**
 * @brief     	从指定地址开始读出指定长度的数据
 * @param     	raddr : 起始地址
 * @param     	pbuf  : 数据指针
 * @param      	length: 要读取的半字(16位)数,即2个字节的整数倍
 * @retval      	无
 */
void stmflash_read(uint32_t raddr, uint16_t *pbuf, uint16_t length)
{
uint16_t i;

    for (i = 0; i < length; i++)
    {
        pbuf[i] = stmflash_read_halfword(raddr); 	/* 读取2个字节 */
        raddr += 2; 									/* 偏移2个字节 */
    }
}

前面也提及到STM32对FLASH写入,其写入地址的值必须是0xFFFFFFFF,所以读函数主要是读取地址的值,以给写函数调用检验,确保能写入成功。读函数实现比较简单,这里就不做展开了。
2. main.c代码
在main.c里面编写如下代码:
const uint8_t g_text_buf[] = {“STM32 FLASH TEST”}; /* 要写入的FLASH字符串数组 */

#define TEXT_LENTH sizeof(g_text_buf) 				/* 数组长度 */

/* SIZE表示半字长(2字节), 大小必须是2的整数倍, 如果不是的话, 强制对齐到2的整数倍 */
#define SIZE TEXT_LENTH / 2 + ((TEXT_LENTH % 2) ? 1 : 0)

/* 设置FLASH 保存地址(必须为偶数,且其值要大于本代码所占用FLASH的大小 + 0X08000000) */
#define FLASH_SAVE_ADDR 0X08070000 

int main(void)
{
    uint8_t key = 0;
    uint16_t i = 0;
uint8_t datatemp[SIZE];

    HAL_Init();                          		/* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);		/* 设置时钟, 72Mhz */
    delay_init(72);                        		/* 延时初始化 */
    usart_init(115200);                  		/* 串口初始化为115200 */
    usmart_dev.init(72);                		/* 初始化USMART */
    led_init();                            		/* 初始化LED */
    lcd_init();                             		/* 初始化LCD */
key_init();                            		/* 初始化按键 */

    lcd_show_string(30,  50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30,  70, 200, 16, 16, "FLASH EEPROM TEST", RED);
    lcd_show_string(30,  90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, 16, "KEY1:Write  KEY0:Read", RED);

    while (1)
    {
        key = key_scan(0);

        if (key == KEY1_PRES) 	/* KEY1按下,写入STM32 FLASH */
        {
            lcd_fill(0, 150, 239, 319, WHITE);
            lcd_show_string(30, 160, 200, 16, 16, "Start Write FLASH....", RED);
            stmflash_write(FLASH_SAVE_ADDR, (uint16_t *)g_text_buf, SIZE);
            lcd_show_string(30, 150, 200, 16, 16, "FLASH Write Finished!", RED);
        }

        if (key == KEY0_PRES)	/* KEY0按下,读取字符串并显示 */
        {
            lcd_show_string(30, 150, 200, 16, 16, "Start Read FLASH.... ", RED);
            stmflash_read(FLASH_SAVE_ADDR, (uint16_t *)datatemp, SIZE);
            lcd_show_string(30, 150, 200, 16, 16, "The Data Readed Is:  ", RED);
            lcd_show_string(30, 170, 200, 16, 16, (char *)datatemp, BLUE);
        }

        i++;
        delay_ms(10);

        if (i == 20)
        {
            LED0_TOGGLE();		/* 提示系统正在运行 */
            i = 0;
        }
    }
}

主函数代码逻辑比较简单,当检测到按键KEY1按下后往FLASH指定地址开始的连续地址空间写入一段数据,当检测到按键KEY0按下后读取FLASH指定地址开始的连续空间数据。
最后,我们将stmflash_read_word和test_write函数加入USMART控制,这样,我们就可以通过串口调试助手,调用STM32F103的FLASH读写函数,方便测试。
45.4 下载验证
将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示的内容如图45.4.1所示:
【正点原子STM32连载】 第四十五章 FLASH模拟EEPROM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第9张图片

图45.4.1程序运行效果图
通过先按KEY1按键写入数据,然后按KEY0读取数据,得到如图45.4.2所示:
【正点原子STM32连载】 第四十五章 FLASH模拟EEPROM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2_第10张图片

图45.4.2 操作后的显示效果图
本实验的测试,我们还可以借助USMART,调用:stmflash_read_word和test_write函数进行测试!

你可能感兴趣的:(stm32,嵌入式硬件,单片机)