完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
第71章 STM32H7的内部Flash应用之模拟EEPROM
本章节为大家讲解STM32H7的内部Flash模拟EEPROM,主要应用到板子没有外置EERPOM的场合,而且H7的内部Flash比较大,可以开辟一个扇区用于模拟EEPROM。
71.1 初学者重要提示
71.2 模拟EEPROM驱动设计
71.4 模拟EEPROM板级支持包(bsp_cpu_flash.c)
71.5 模拟EERPOM驱动移植和使用
71.6 实验例程设计框架
71.7 实验例程说明(MDK)
71.8 实验例程说明(IAR)
71.9 总结
71.1 初学者重要提示
- 学习本章节前,务必优先学习第70章。
- 使用内部Flash模拟EEPROM,务必告诉编译要使用的存储空间,防止这个空间存入了程序。
- STM32H7的Flash编程时,务必保证要编程的地址是32字节对齐的,即此地址对32求余为0。并且编程的数据必须32字节整数倍。
- STM32H743XI有两个独立的BANK,一个BANK的编程和擦除操作对另一个BANK没有任何影响。但是用户应用程序和要擦写的Flash扇区在同一个BANK,在执行擦写操作时,应用应用程序将停止运行,包括中断服务程序。
- 使用内部Flash模拟EEPROM要做到先擦除后使用。
71.2 模拟EEPROM驱动设计
这里重点把内部Flash的读取,编程和擦除做个说明。
71.2.1 内部Flash擦除的实现
内部Flash的擦除思路如下:
- 第1步,获取擦除地址所处的扇区。
- 第2步,调用函数HAL_FLASH_Unlock解锁。
- 第3步,调用函数HAL_FLASHEx_Erase擦除一个扇区。
- 第4步,调用函数HAL_FLASH_Lock上锁。
按照这个思路,程序实现如下:
1. /* 2. ****************************************************************************************************** 3. * 函 数 名: bsp_EraseCpuFlash 4. * 功能说明: 擦除CPU FLASH一个扇区 (128KB) 5. * 形 参: _ulFlashAddr : Flash地址 6. * 返 回 值: 0 成功, 1 失败 7. * HAL_OK = 0x00, 8. * HAL_ERROR = 0x01, 9. * HAL_BUSY = 0x02, 10. * HAL_TIMEOUT = 0x03 11. * 12. ****************************************************************************************************** 13. */ 14. uint8_t bsp_EraseCpuFlash(uint32_t _ulFlashAddr) 15. { 16. uint32_t FirstSector = 0, NbOfSectors = 0; 17. FLASH_EraseInitTypeDef EraseInitStruct; 18. uint32_t SECTORError = 0; 19. uint8_t re; 20. 21. /* 解锁 */ 22. HAL_FLASH_Unlock(); 23. 24. /* 获取此地址所在的扇区 */ 25. FirstSector = bsp_GetSector(_ulFlashAddr); 26. 27. /* 固定1个扇区 */ 28. NbOfSectors = 1; 29. 30. /* 擦除扇区配置 */ 31. EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS; 32. EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3; 33. 34. if (_ulFlashAddr >= ADDR_FLASH_SECTOR_0_BANK2) 35. { 36. EraseInitStruct.Banks = FLASH_BANK_2; 37. } 38. else 39. { 40. EraseInitStruct.Banks = FLASH_BANK_1; 41. } 42. 43. EraseInitStruct.Sector = FirstSector; 44. EraseInitStruct.NbSectors = NbOfSectors; 45. 46. /* 扇区擦除 */ 47. re = HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError); 48. 49. /* 擦除完毕后,上锁 */ 50. HAL_FLASH_Lock(); 51. 52. return re; 53. }
这里将此程序设计的关键点为大家做个说明:
- 第25行函数是通过函数bsp_GetSector获取要擦除地址所处的扇区。这个函数的实现比较简单,代码如下:
/* ********************************************************************************************************* * 函 数 名: bsp_GetSector * 功能说明: 根据地址计算扇区首地址 * 形 参: 无 * 返 回 值: 扇区号(0-7) ********************************************************************************************************* */ uint32_t bsp_GetSector(uint32_t Address) { uint32_t sector = 0; if (((Address < ADDR_FLASH_SECTOR_1_BANK1) && (Address >= ADDR_FLASH_SECTOR_0_BANK1)) || \ ((Address < ADDR_FLASH_SECTOR_1_BANK2) && (Address >= ADDR_FLASH_SECTOR_0_BANK2))) { sector = FLASH_SECTOR_0; } else if (((Address < ADDR_FLASH_SECTOR_2_BANK1) && (Address >= ADDR_FLASH_SECTOR_1_BANK1)) || \ ((Address < ADDR_FLASH_SECTOR_2_BANK2) && (Address >= ADDR_FLASH_SECTOR_1_BANK2))) { sector = FLASH_SECTOR_1; } else if (((Address < ADDR_FLASH_SECTOR_3_BANK1) && (Address >= ADDR_FLASH_SECTOR_2_BANK1)) || \ ((Address < ADDR_FLASH_SECTOR_3_BANK2) && (Address >= ADDR_FLASH_SECTOR_2_BANK2))) { sector = FLASH_SECTOR_2; } else if (((Address < ADDR_FLASH_SECTOR_4_BANK1) && (Address >= ADDR_FLASH_SECTOR_3_BANK1)) || \ ((Address < ADDR_FLASH_SECTOR_4_BANK2) && (Address >= ADDR_FLASH_SECTOR_3_BANK2))) { sector = FLASH_SECTOR_3; } else if (((Address < ADDR_FLASH_SECTOR_5_BANK1) && (Address >= ADDR_FLASH_SECTOR_4_BANK1)) || \ ((Address < ADDR_FLASH_SECTOR_5_BANK2) && (Address >= ADDR_FLASH_SECTOR_4_BANK2))) { sector = FLASH_SECTOR_4; } else if (((Address < ADDR_FLASH_SECTOR_6_BANK1) && (Address >= ADDR_FLASH_SECTOR_5_BANK1)) || \ ((Address < ADDR_FLASH_SECTOR_6_BANK2) && (Address >= ADDR_FLASH_SECTOR_5_BANK2))) { sector = FLASH_SECTOR_5; } else if (((Address < ADDR_FLASH_SECTOR_7_BANK1) && (Address >= ADDR_FLASH_SECTOR_6_BANK1)) || \ ((Address < ADDR_FLASH_SECTOR_7_BANK2) && (Address >= ADDR_FLASH_SECTOR_6_BANK2))) { sector = FLASH_SECTOR_6; } else if (((Address < ADDR_FLASH_SECTOR_0_BANK2) && (Address >= ADDR_FLASH_SECTOR_7_BANK1)) || \ ((Address < CPU_FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_7_BANK2))) { sector = FLASH_SECTOR_7; } else { sector = FLASH_SECTOR_7; } return sector; }
由于STM32H7的BANK1和BANK2是独立的,都有8个扇区,所以程序里面只需返回要擦除地址所处的扇区号即可。
- 第47行的擦除函数HAL_FLASHEx_Erase在第70章的4.4小节有说明。
71.2.2 内部Flash编程的实现
内部Flash的编程思路如下:
- 第1步,判断是否要编写数据进去,如果数据已经在内部Flash里面。
- 第2步,调用函数HAL_FLASH_Unlock解锁。
- 第3步,调用函数HAL_FLASH_Program对内部Flash编程数据。
- 第4步,调用函数HAL_FLASH_Lock上锁。
按照这个思路,程序实现如下:
1. /* 2. ****************************************************************************************************** 3. * 函 数 名: bsp_WriteCpuFlash 4. * 功能说明: 写数据到CPU 内部Flash。 必须按32字节整数倍写。不支持跨扇区。扇区大小128KB. \ 5. * 写之前需要擦除扇区. 长度不是32字节整数倍时,最后几个字节末尾补0写入. 6. * 形 参: _ulFlashAddr : Flash地址 7. * _ucpSrc : 数据缓冲区 8. * _ulSize : 数据大小(单位是字节, 必须是32字节整数倍) 9. * 返 回 值: 0-成功,1-数据长度或地址溢出,2-写Flash出错(估计Flash寿命到) 10. ****************************************************************************************************** 11. */ 12. uint8_t bsp_WriteCpuFlash(uint32_t _ulFlashAddr, uint8_t *_ucpSrc, uint32_t _ulSize) 13. { 14. uint32_t i; 15. uint8_t ucRet; 16. 17. /* 如果偏移地址超过芯片容量,则不改写输出缓冲区 */ 18. if (_ulFlashAddr + _ulSize > CPU_FLASH_BASE_ADDR + CPU_FLASH_SIZE) 19. { 20. return 1; 21. } 22. 23. /* 长度为0时不继续操作 */ 24. if (_ulSize == 0) 25. { 26. return 0; 27. } 28. 29. ucRet = bsp_CmpCpuFlash(_ulFlashAddr, _ucpSrc, _ulSize); 30. 31. if (ucRet == FLASH_IS_EQU) 32. { 33. return 0; 34. } 35. 36. __set_PRIMASK(1); /* 关中断 */ 37. 38. /* FLASH 解锁 */ 39. HAL_FLASH_Unlock(); 40. 41. for (i = 0; i < _ulSize / 32; i++) 42. { 43. uint64_t FlashWord[4]; 44. 45. memcpy((char *)FlashWord, _ucpSrc, 32); 46. _ucpSrc += 32; 47. 48. if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, _ulFlashAddr, 49. (uint64_t)((uint32_t)FlashWord)) == HAL_OK) 50. { 51. _ulFlashAddr = _ulFlashAddr + 32; /* 递增,操作下一个256bit */ 52. } 53. else 54. { 55. goto err; 56. } 57. } 58. 59. /* 长度不是32字节整数倍 */ 60. if (_ulSize % 32) 61. { 62. uint64_t FlashWord[4]; 63. 64. FlashWord[0] = 0; 65. FlashWord[1] = 0; 66. FlashWord[2] = 0; 67. FlashWord[3] = 0; 68. memcpy((char *)FlashWord, _ucpSrc, _ulSize % 32); 69. if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, _ulFlashAddr, 70. (uint64_t)((uint32_t)FlashWord)) == HAL_OK) 71. { 72. ; // _ulFlashAddr = _ulFlashAddr + 32; 73. 74. } 75. else 76. { 77. goto err; 78. } 79. } 80. 81. /* Flash 加锁,禁止写Flash控制寄存器 */ 82. HAL_FLASH_Lock(); 83. 84. __set_PRIMASK(0); /* 开中断 */ 85. 86. return 0; 87. 88. err: 89. /* Flash 加锁,禁止写Flash控制寄存器 */ 90. HAL_FLASH_Lock(); 91. 92. __set_PRIMASK(0); /* 开中断 */ 93. 94. return 1; 95. }
关于此函数有几个要点:
- 第1个参数必须32字节对齐,即要编程的Flash地址对32求余为0。
- 第3个参数必须是32字节的整数倍,长度不是32字节整数倍时,最后几个字节补0写入。
- 第29行,函数bsp_CmpCpuFlash放在这里只有一个作用,判断将要写入的数据是否已经在内部Flash存在,如果已经存在,无需重复写入,直接返回。
- 第36行,做了一个关中断操作,这里有个知识点要给大家普及下,由于H7的BANK1和BANK2是独立的,当前正在擦除的扇区所处的BANK会停止所有在此BANK执行的程序,包含中断也会停止执行。比如当前的应用程序都在BANK1,如果要擦除或者编程BANK2,是不会不影响BANK1里面执行的程序。这里安全起见加上了开关中断。
- 第41到57行,先将32字节整数倍的数据通过函数HAL_FLASH_Program编程,此函数每次可以固定编程32字节数据。
- 第60到79行,将剩余不足32字节的数据补0,凑齐32字节编程。
71.2.3 内部Flash读取的实现
内部Flash数据读取比较简单,采用总线方式读取,跟访问内部RAM是一样的。比如要读取地址
0x08100000里面的一个32bit变量,我们就可以:
变量 = *(uint32_t *)(0x08100000)
为了方便起见,也专门准备了一个函数:
/* ********************************************************************************************************* * 函 数 名: bsp_ReadCpuFlash * 功能说明: 读取CPU Flash的内容 * 形 参: _ucpDst : 目标缓冲区 * _ulFlashAddr : 起始地址 * _ulSize : 数据大小(单位是字节) * 返 回 值: 0=成功,1=失败 ********************************************************************************************************* */ uint8_t bsp_ReadCpuFlash(uint32_t _ulFlashAddr, uint8_t *_ucpDst, uint32_t _ulSize) { uint32_t i; if (_ulFlashAddr + _ulSize > CPU_FLASH_BASE_ADDR + CPU_FLASH_SIZE) { return 1; } /* 长度为0时不继续操作,否则起始地址为奇地址会出错 */ if (_ulSize == 0) { return 1; } for (i = 0; i < _ulSize; i++) { *_ucpDst++ = *(uint8_t *)_ulFlashAddr++; } return 0; }
71.2.4 告诉编译器使用的扇区(重要)
使用内部Flash模拟EEPROM切不可随意定义一个扇区使用。因为编译器并不知道用户使用了这个扇区,导致应用程序也会编程到此扇区里面,所以就需要告诉编译器。
告诉MDK的方法如下(0x0810 0000是H7的BANK2首地址):
const uint8_t para_flash_area[128*1024] __attribute__((at(0x08100000)));
告诉IAR的方法如下:
#pragma location=0x08100000 const uint8_t para_flash_area[128*1024];
这里有两点特别注意:
- 模拟EEPROM的扇区可以定义到从第2个扇区开始的任何扇区,但不可以定义到首扇区,因为这个扇区是默认的boot启动地址。
- 如果应用程序不大的话,不推荐定义到末尾扇区,以MDK为例,定义到末尾扇区后,会导致整个Flash空间都被使用,从而让程序下载下载时间变长。
71.3 模拟EEPROM板级支持包(bsp_cpu_flash.c)
模拟EEPROM的驱动文件bsp_cpu_flash.c主要实现了如下几个API供用户调用:
- bsp_GetSector
- bsp_ReadCpuFlash
- bsp_CmpCpuFlash
- bsp_EraseCpuFlash
- bsp_WriteCpuFlash
71.3.1 函数bsp_GetSector
函数原型:
uint32_t bsp_GetSector(uint32_t Address)
函数描述:
此函数主要用于获取给定地址所处的扇区。
函数参数:
- 第1个参数是用户给定的地址。
- 返回值,范围FLASH_SECTOR_0到FLASH_SECTOR_7。
71.3.2 函数bsp_ReadCpuFlash
函数原型:
uint8_t bsp_ReadCpuFlash(uint32_t _ulFlashAddr, uint8_t *_ucpDst, uint32_t _ulSize)
函数描述:
此函数用于从内部Flash读取数据
函数参数:
- 第1个参数读取的起始地址。
- 第2个参数是读取数据的存储地址
- 第3个参数是读取数据的大小,单位字节。
- 返回值,0表示成功,1表示失败。
71.3.3 函数bsp_CmpCpuFlash
函数原型:
uint8_t bsp_CmpCpuFlash(uint32_t _ulFlashAddr, uint8_t *_ucpBuf, uint32_t _ulSize)
函数描述:
要编程的数据是否在内部Flash已经存在。
函数参数:
- 第1个参数是内部Flash地址。
- 第2个参数是缓冲区地址。
- 第3个参数是数据大小,单位字节。
- 返回值:
FLASH_IS_EQU 0 Flash内容和待写入的数据相等,不需要擦除和写操作。
FLASH_REQ_WRITE 1 Flash不需要擦除,直接写。
FLASH_REQ_ERASE 2 Flash需要先擦除,再写。
FLASH_PARAM_ERR 3 函数参数错误。
71.3.4 函数bsp_EraseCpuFlash
函数原型:
uint8_t bsp_EraseCpuFlash(uint32_t _ulFlashAddr)
函数描述:
此函数用于擦除一个扇区,大小128KB
函数参数:
- 第1个参数要擦除的扇区地址,可以是此扇区范围内的任意值,一般填扇区首地址即可。
- 返回值:
HAL_OK = 0x00
HAL_ERROR = 0x01
HAL_BUSY = 0x02
HAL_TIMEOUT = 0x03
71.3.5 函数bsp_WriteCpuFlash
函数原型:
uint8_t bsp_WriteCpuFlash(uint32_t _ulFlashAddr, uint8_t *_ucpSrc, uint32_t _ulSize)
函数描述:
此函数用于编程数据到内部Flash。
函数参数:
- 第1个参数是要编程的内部Flash地址。
- 第2个参数是数据缓冲区地址。
- 第3个参数是数据大小,单位字节。
- 返回值,0-成功,1-数据长度或地址溢出,2-写Flash出错(估计Flash寿命到)。
注意事项:
- 第1个参数必须32字节对齐,即要编程的Flash地址对32求余为0。
- 第3个参数必须是32字节的整数倍,长度不是32字节整数倍时,此函数会将几个字节补0写入
71.4 模拟EEPROM驱动移植和使用
模拟EEPROM移植步骤如下:
- 第1步:复制bsp_cpu_flash.c和bsp_cpu_flash.h到自己的工程目录,并添加到工程里面。
- 第2步:Flash驱动文件主要用到HAL库的Flash驱动文件,简单省事些可以添加所有HAL库C源文件进来。
- 第3步,应用方法看本章节配套例子即可。
71.5 实验例程设计框架
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:
第1阶段,上电启动阶段:
- 这部分在第14章进行了详细说明。
第2阶段,进入main函数:
- 第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
- 第2部分,应用程序设计部分,实现内部Flash模拟EEPROM。
71.6 实验例程说明(MDK)
配套例子:
V7-049_内部Flash模拟EEPROM
实验目的:
- 学习内部Flash模拟EEPROM。
实验内容:
- 使用内部Flash模拟EEPROM,务必告诉编译要使用的存储空间,防止这个空间存入了程序。
- 对于同一个地址空间,仅支持一次编程(不推荐二次编程,即使是将相应bit由数值1编程0)。
- 只能对已经擦除的空间做编程,擦除1个扇区是128KB。
- H7的Flash编程时,务必保证要编程的地址是32字节对齐的,即此地址对32求余为0。
并且编程的数据必须32字节整数倍,函数bsp_WriteCpuFlash对字节数不够32字节整数倍的情况自动补0。
实验操作:
- K1键按下,将8bit,16bit和32bit数据写入到内部Flash。
- K2键按下,将结构体数据写入到内部Flash。
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1。
程序设计:
系统栈大小分配:
RAM空间用的DTCM:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* 配置MPU */ MPU_Config(); /* 使能L1 Cache */ CPU_CACHE_Enable(); /* STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟: - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。 - 设置NVIV优先级分组为4。 */ HAL_Init(); /* 配置系统时钟到400MHz - 切换使用HSE。 - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。 - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder并开启 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitDWT(); /* 初始化DWT时钟周期计数器 */ bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */ bsp_InitTimer(); /* 初始化滴答定时器 */ bsp_InitLPUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */ bsp_InitLed(); /* 初始化LED */ bsp_InitExtSDRAM(); /* 初始化SDRAM */ }
MPU配置和Cache配置:
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
/* ********************************************************************************************************* * 函 数 名: MPU_Config * 功能说明: 配置MPU * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void MPU_Config( void ) { MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */ HAL_MPU_Disable(); /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x24000000; MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x60000000; MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER1; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /*使能 MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } /* ********************************************************************************************************* * 函 数 名: CPU_CACHE_Enable * 功能说明: 使能L1 Cache * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void CPU_CACHE_Enable(void) { /* 使能 I-Cache */ SCB_EnableICache(); /* 使能 D-Cache */ SCB_EnableDCache(); }
每10ms调用一次蜂鸣器处理:
蜂鸣器处理是在滴答定时器中断里面实现,每10ms执行一次检测。
/* ********************************************************************************************************* * 函 数 名: bsp_RunPer10ms * 功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求 * 不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_RunPer10ms(void) { bsp_KeyScan10ms(); }
主功能:
主程序实现如下操作:
- 启动一个自动重装软件定时器,每100ms翻转一次LED2。
- K1键按下,将8bit,16bit和32bit数据写入到内部Flash。
- K2键按下,将结构体数据写入到内部Flash。
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: c程序入口 * 形 参: 无 * 返 回 值: 错误代码(无需处理) ********************************************************************************************************* */ int main(void) { uint8_t ucKeyCode; /* 按键代码 */ uint8_t ucTest, *ptr8; uint16_t uiTest, *ptr16; uint32_t ulTest, *ptr32; PARAM_T tPara, *paraptr; /* 初始化数据 */ tPara.Baud485 = 0x5555AAAA; tPara.ParamVer = 0x99; tPara.ucBackLight = 0x7788; tPara.ucRadioMode = 99.99f; bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名称和版本等信息 */ PrintfHelp(); /* 打印操作提示 */ bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */ while (1) { bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */ /* 判断定时器超时时间 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 进来一次 */ bsp_LedToggle(2); } /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */ ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1键按下,将8bit,16bit和32bit数据写入到内部Flash */ /* 1、对于同一个地址空间,仅支持一次编程(不推荐二次编程,即使是将相应bit由数值1编程0)。 2、只能对已经擦除的空间做编程,擦除1个扇区是128KB。 3、H7的Flash编程时,务必保证要编程的地址是32字节对齐的,即此地址对32求余为0。并且编 程的数据必须32字节整数倍。函数bsp_WriteCpuFlash对字节数不够32字节整数倍的情况自动补 0。 */ /* 擦除扇区 */ bsp_EraseCpuFlash((uint32_t)para_flash_area); ucTest = 0xAA; uiTest = 0x55AA; ulTest = 0x11223344; /* 扇区写入数据 */ bsp_WriteCpuFlash((uint32_t)para_flash_area + 32*0, (uint8_t *)&ucTest, sizeof(ucTest)); bsp_WriteCpuFlash((uint32_t)para_flash_area + 32*1, (uint8_t *)&uiTest, sizeof(uiTest)); bsp_WriteCpuFlash((uint32_t)para_flash_area + 32*2, (uint8_t *)&ulTest, sizeof(ulTest)); /* 读出数据并打印 */ ptr8 = (uint8_t *)(para_flash_area + 32*0); ptr16 = (uint16_t *)(para_flash_area + 32*1); ptr32 = (uint32_t *)(para_flash_area + 32*2); printf("写入数据:ucTest = %x, uiTest = %x, ulTest = %x\r\n", ucTest, uiTest, ulTest); printf("读取数据:ptr8 = %x, ptr16 = %x, ptr32 = %x\r\n", *ptr8, *ptr16, *ptr32); break; case KEY_DOWN_K2: /* K2键按下, 将结构体数据写入到内部Flash */ /* 擦除扇区 */ bsp_EraseCpuFlash((uint32_t)para_flash_area); /* 扇区写入数据 */ bsp_WriteCpuFlash((uint32_t)para_flash_area, (uint8_t *)&tPara, sizeof(tPara)); /* 读出数据并打印 */ paraptr = (PARAM_T *)((uint32_t)para_flash_area); printf("写入数据:Baud485=%x, ParamVer=%x, ucBackLight=%x, ucRadioMode=%f\r\n", tPara.Baud485, tPara.ParamVer, tPara.ucBackLight, paraptr->ucRadioMode); printf("读取数据:Baud485=%x, ParamVer=%x, ucBackLight=%x, ucRadioMode=%f\r\n", paraptr->Baud485, paraptr->ParamVer, paraptr->ucBackLight, paraptr->ucRadioMode); break; default: /* 其它的键值不处理 */ break; } } } }
71.7 实验例程说明(IAR)
配套例子:
V7-049_内部Flash模拟EEPROM
实验目的:
- 学习内部Flash模拟EEPROM。
实验内容:
- 使用内部Flash模拟EEPROM,务必告诉编译要使用的存储空间,防止这个空间存入了程序。
- 对于同一个地址空间,仅支持一次编程(不推荐二次编程,即使是将相应bit由数值1编程0)。
- 只能对已经擦除的空间做编程,擦除1个扇区是128KB。
- H7的Flash编程时,务必保证要编程的地址是32字节对齐的,即此地址对32求余为0。
并且编程的数据必须32字节整数倍,函数bsp_WriteCpuFlash对字节数不够32字节整数倍的情况自动补0。
实验操作:
- K1键按下,将8bit,16bit和32bit数据写入到内部Flash。
- K2键按下,将结构体数据写入到内部Flash。
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1。
程序设计:
系统栈大小分配:
RAM空间用的DTCM:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* 配置MPU */ MPU_Config(); /* 使能L1 Cache */ CPU_CACHE_Enable(); /* STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟: - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。 - 设置NVIV优先级分组为4。 */ HAL_Init(); /* 配置系统时钟到400MHz - 切换使用HSE。 - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。 - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder并开启 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitDWT(); /* 初始化DWT时钟周期计数器 */ bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */ bsp_InitTimer(); /* 初始化滴答定时器 */ bsp_InitLPUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */ bsp_InitLed(); /* 初始化LED */ bsp_InitExtSDRAM(); /* 初始化SDRAM */ }
MPU配置和Cache配置:
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
/* ********************************************************************************************************* * 函 数 名: MPU_Config * 功能说明: 配置MPU * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void MPU_Config( void ) { MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */ HAL_MPU_Disable(); /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x24000000; MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x60000000; MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER1; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /*使能 MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } /* ********************************************************************************************************* * 函 数 名: CPU_CACHE_Enable * 功能说明: 使能L1 Cache * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void CPU_CACHE_Enable(void) { /* 使能 I-Cache */ SCB_EnableICache(); /* 使能 D-Cache */ SCB_EnableDCache(); }
每10ms调用一次蜂鸣器处理:
蜂鸣器处理是在滴答定时器中断里面实现,每10ms执行一次检测。
/* ********************************************************************************************************* * 函 数 名: bsp_RunPer10ms * 功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求 * 不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_RunPer10ms(void) { bsp_KeyScan10ms(); }
主功能:
主程序实现如下操作:
- 启动一个自动重装软件定时器,每100ms翻转一次LED2。
- K1键按下,将8bit,16bit和32bit数据写入到内部Flash。
- K2键按下,将结构体数据写入到内部Flash。
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: c程序入口 * 形 参: 无 * 返 回 值: 错误代码(无需处理) ********************************************************************************************************* */ int main(void) { uint8_t ucKeyCode; /* 按键代码 */ uint8_t ucTest, *ptr8; uint16_t uiTest, *ptr16; uint32_t ulTest, *ptr32; PARAM_T tPara, *paraptr; /* 初始化数据 */ tPara.Baud485 = 0x5555AAAA; tPara.ParamVer = 0x99; tPara.ucBackLight = 0x7788; tPara.ucRadioMode = 99.99f; bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名称和版本等信息 */ PrintfHelp(); /* 打印操作提示 */ bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */ while (1) { bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */ /* 判断定时器超时时间 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 进来一次 */ bsp_LedToggle(2); } /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */ ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1键按下,将8bit,16bit和32bit数据写入到内部Flash */ /* 1、对于同一个地址空间,仅支持一次编程(不推荐二次编程,即使是将相应bit由数值1编程0)。 2、只能对已经擦除的空间做编程,擦除1个扇区是128KB。 3、H7的Flash编程时,务必保证要编程的地址是32字节对齐的,即此地址对32求余为0。并且编 程的数据必须32字节整数倍。函数bsp_WriteCpuFlash对字节数不够32字节整数倍的情况自动补 0。 */ /* 擦除扇区 */ bsp_EraseCpuFlash((uint32_t)para_flash_area); ucTest = 0xAA; uiTest = 0x55AA; ulTest = 0x11223344; /* 扇区写入数据 */ bsp_WriteCpuFlash((uint32_t)para_flash_area + 32*0, (uint8_t *)&ucTest, sizeof(ucTest)); bsp_WriteCpuFlash((uint32_t)para_flash_area + 32*1, (uint8_t *)&uiTest, sizeof(uiTest)); bsp_WriteCpuFlash((uint32_t)para_flash_area + 32*2, (uint8_t *)&ulTest, sizeof(ulTest)); /* 读出数据并打印 */ ptr8 = (uint8_t *)(para_flash_area + 32*0); ptr16 = (uint16_t *)(para_flash_area + 32*1); ptr32 = (uint32_t *)(para_flash_area + 32*2); printf("写入数据:ucTest = %x, uiTest = %x, ulTest = %x\r\n", ucTest, uiTest, ulTest); printf("读取数据:ptr8 = %x, ptr16 = %x, ptr32 = %x\r\n", *ptr8, *ptr16, *ptr32); break; case KEY_DOWN_K2: /* K2键按下, 将结构体数据写入到内部Flash */ /* 擦除扇区 */ bsp_EraseCpuFlash((uint32_t)para_flash_area); /* 扇区写入数据 */ bsp_WriteCpuFlash((uint32_t)para_flash_area, (uint8_t *)&tPara, sizeof(tPara)); /* 读出数据并打印 */ paraptr = (PARAM_T *)((uint32_t)para_flash_area); printf("写入数据:Baud485=%x, ParamVer=%x, ucBackLight=%x, ucRadioMode=%f\r\n", tPara.Baud485, tPara.ParamVer, tPara.ucBackLight, paraptr->ucRadioMode); printf("读取数据:Baud485=%x, ParamVer=%x, ucBackLight=%x, ucRadioMode=%f\r\n", paraptr->Baud485, paraptr->ParamVer, paraptr->ucBackLight, paraptr->ucRadioMode); break; default: /* 其它的键值不处理 */ break; } } } }
71.8 总结
本章节就为大家讲解这么多, 实际应用中的注意事项比较多,应用到项目之前务必实际测试熟悉下。