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

news2024/12/26 10:50:28

第四十五章 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所示:
在这里插入图片描述

表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所示:
在这里插入图片描述

图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所示:
在这里插入图片描述

图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所示:
在这里插入图片描述

图45.1.3.2 FLASH_KEYR寄存器
该寄存器主要用来解锁FPEC,必须在该寄存器写入特定的序列(KEY1和KEY2)解锁后,才能对FLASH_CR寄存器进行写操作。
 FLASH控制寄存器(FLASH_CR)
FLASH控制寄存器描述如图45.1.3.3所示:
在这里插入图片描述

图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所示:
在这里插入图片描述

图45.1.3.4 FLASH_SR寄存器
该寄存器主要用来指示当前FPEC的操作编程状态。由于寄存器中描述比较详细,这里就不重复了。
 闪存地址寄存器(FLASH_AR)
闪存地址寄存器描述如图45.1.3.5所示:
在这里插入图片描述

图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 程序流程图
在这里插入图片描述

图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所示:
在这里插入图片描述

图45.4.1程序运行效果图
通过先按KEY1按键写入数据,然后按KEY0读取数据,得到如图45.4.2所示:
在这里插入图片描述

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

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

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

相关文章

设计模式第18讲——中介者模式(Mediator)

目录 一、什么是中介者模式 二、角色组成 三、优缺点 四、应用场景 4.1 生活场景 4.2 java场景 五、代码实现 5.0 代码结构 5.1 抽象中介者&#xff08;Mediator&#xff09;——LogisticsCenter 5.2 抽象同事类&#xff08;Colleague&#xff09;——Participant 5…

nvm安装node

使用 Windows 系统的我选择使用其推荐的 nvm-windows 来管理 Node.js 版本。 在安装 nvm-windows 前&#xff0c;如果你的电脑中已经安装了 Node.js&#xff0c;那么可以选择卸载&#xff0c;也可以选择不卸载。因为在安装 nvm-windows 的过程中其会询问你是否需要将已安装的 N…

mysql数据库迁移到kingbase人大金仓

1. 启动数据迁移工具 2. 浏览器打开网址[http://localhost:8080/]进入可视化操作界面&#xff0c;在源数据库添加人大金仓数据库信息&#xff0c;测试成功后保存 3.在目标数据库填写需要同步的mysql数据库&#xff0c;添加对应的mysql数据库信息&#xff0c;测试成功后保存 4.在…

The Company Requires Superficial StudyPHP 打开执行PHP ②

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; PHP MYSQL &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &#x1f44…

自制游戏引擎

这是一个玩具 1. 引擎使用流程 SmallEngine是引擎的核心模块,封装渲染功能和场景管理功能等Editor是编辑器,类似unity和ue编辑器,能够动态添加对象和组件Sandbox是游戏播放器,能够运行游戏 2. SmallEngine 参考 https://www.bilibili.com/video/BV1KE41117BD/?spm_id_from333…

ARM实验-ARM主程序调用ARM/C语言子程序

一、实验名称&#xff1a;ARM主程序调用ARM/C语言子程序 二、实验目的&#xff1a; 了解ARM应用程序框架。了解ARM汇编程序函数和C语言程序函数相互调用时&#xff0c;遵循的ATPCS标准&#xff1b;了解和掌握ARM汇编程序调用C语言程序函数的基本方法&#xff1b;了解和掌握AR…

操作系统第4章 文件系统 知识点

UNIX系统不存一些具体的指针了 只存文件名和指向i结点的指针 这个删除和截断有什么区别 目录本来放在外存的&#xff0c;有文件打开表&#xff0c;从外存复制到内存的文件打开表中&#xff0c;用户想继续读的时候&#xff0c;不用再去外存搜索目录 访问文件打开表的索引叫文…

基于“SRP模型+”多技术融合在生态环境脆弱性评价模型构建、时空格局演变分析与RSEI 指数的生态质量评价及拓展应用

近年来&#xff0c;国内外学者在生态系统的敏感性、适应能力和潜在影响等方面开展了大量的生态脆弱性研究&#xff0c;他们普遍将生态脆弱性概念与农牧交错带、喀斯特地区、黄土高原区、流域、城市等相结合&#xff0c;评价不同类型研究区的生态脆弱特征&#xff0c;其研究内容…

大数据Doris(五十二):Doris数据导出案例和注意事项

文章目录 Doris数据导出案例和注意事项 一、Doris数据导出到HDFS案例 1、创建Doris表并插入数据 2、创建Export ,数据导出到 HDFS 3、查看任务 4、查看导出结果 二、Doris数据导出到本地案例 1、配置 fe.conf 2、Doris 数据导出到本地 三、注意事项 Doris数据导出案例…

kettle源码远程debug调试

一、kettle启动时指定debug端口号 windows下&#xff0c;修改bat执行文件&#xff0c;同理 linux修改sh执行文件 在java执行参数的末尾添加debug参数 address为debug端口 -Xdebug -Xnoagent -Djava.compilerNONE -Xrunjdwp:transportdt_socket,servery,suspendn,address9080然…

从渲染流程、数据处理结构聊聊Flutter性能优化

不可否认 Flutter 是一个非常强大的移动应用开发框架&#xff0c;我们在技术架构选型时就是选用的 Flutter&#xff0c;特别是跨端能力属实很优秀&#xff0c;but 也逐渐发现在复杂的应用程序实现中&#xff0c;App 的性能会受到一些影响。 其实这个问题&#xff0c;我们内部也…

SpringCloud入门实战(九)- SpringCloud Config配置中心

&#x1f4dd; 学技术、更要掌握学习的方法&#xff0c;一起学习&#xff0c;让进步发生 &#x1f469;&#x1f3fb; 作者&#xff1a;一只IT攻城狮 &#xff0c;关注我&#xff0c;不迷路 。 &#x1f490;学习建议&#xff1a;1、养成习惯&#xff0c;学习java的任何一个技术…

java List集合使用笔记

1、List集合的特点 有序集合、有序的序列&#xff0c;用户可以精准的控制元素插入的位置通过索引访问元素可以搜索元素与Set不同&#xff0c;List允许重复的元素存在 2、List集合特有的方法 add(索引,元素) remove(索引)&#xff1a;返回的是被删除的索引 get(索引)&#xf…

Arduino uno 环境配置 for Mac

1、IDE 在官网下载 官网地址&#xff1a;https://www.arduino.cc/en/software 看到钱&#x1f4b0;不要怕&#xff0c;只是问你捐不捐款&#xff0c;不收钱&#xff0c;你直接安装就行 &#xff08;你也可以捐一点&#xff5e;&#xff09; 安装之后 2、安装驱动 地址 &…

单片机-矩阵键盘密码锁

89C52RC芯片 1.矩阵按键输入正确密码&#xff0c;LCD1602右上角显示ok&#xff0c;错误显示Err。 涉及文件&#xff1a; 1.main.c (#include<regx52.h>) 2.lcd1602.c lcd1602.h 3.Delay.c Delay.h 4.MatrixKey.c MetrixKey.h 共7项 代码 main.c #…

此导入从不用作值,必须使用 “import type“ ,因为 “importsNotUsedAsValues“ 设置为 “error“。

前言 最近电脑更新了一次系统&#xff0c;重启后在 VsCode中打开项目 &#xff0c;发现原本正常的代码出现了一堆语法提示。网上搜了一下&#xff0c;没有找到关于此问题的回答&#xff0c;不知道我是不是第一个遇到的。在此记录一下这次的经历&#xff0c;如果有其他人遇到&a…

蓝桥杯专题-试题版含答案-【6174问题】【笨小熊】【鸡兔同笼】【小学生算数】

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

突破APP广告收益天花板的多种数据优化方法

​APP开发者对于广告变现最关心的问题就是收益。事实上&#xff0c;广告收益与广告请求、曝光和点击等关键数据之间存在着密切的联系。这些数据的表现直接影响着广告的收益情况。 因此&#xff0c;开发者需要重视并优化这些关键链路数据。本文将介绍一些优化方法&#xff0c;帮…

zabbix 介绍及部署

目录 一、zabbix的基本概述 二、zabbix功能 &#xff08;一&#xff09;数据收集 &#xff08;二&#xff09;灵活的阈值定义 &#xff08;三&#xff09;高度可配置的告警 &#xff08;四&#xff09;实时图形 &#xff08;五&#xff09;网络监控功能 &#xff08;六…

牛客网专项练习——C语言错题集(10)

文章目录 两数相除后的数据类型if 语句表达式类似转义字符逗号表达式易错题指针概念异或刁钻题&#xff0c;多维数组地址自动变量 两数相除后的数据类型 C语言规定除法运算符( / )的运算结果的数据类型与被除数的数据类型保存一致&#xff0c;所以一个整数除以另一个整数的结果…